Merge "Add arguments to get-display." into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 605ce22..c8046ab 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -16,11 +16,13 @@
     ":android.app.usage.flags-aconfig-java{.generated_srcjars}",
     ":android.companion.flags-aconfig-java{.generated_srcjars}",
     ":android.content.pm.flags-aconfig-java{.generated_srcjars}",
+    ":android.content.res.flags-aconfig-java{.generated_srcjars}",
     ":android.hardware.radio.flags-aconfig-java{.generated_srcjars}",
     ":android.nfc.flags-aconfig-java{.generated_srcjars}",
     ":android.os.flags-aconfig-java{.generated_srcjars}",
     ":android.os.vibrator.flags-aconfig-java{.generated_srcjars}",
     ":android.security.flags-aconfig-java{.generated_srcjars}",
+    ":android.service.notification.flags-aconfig-java{.generated_srcjars}",
     ":android.view.flags-aconfig-java{.generated_srcjars}",
     ":android.view.accessibility.flags-aconfig-java{.generated_srcjars}",
     ":camera_platform_flags_core_java_lib{.generated_srcjars}",
@@ -50,6 +52,7 @@
     ":aconfig_midi_flags_java_lib{.generated_srcjars}",
     ":android.service.autofill.flags-aconfig-java{.generated_srcjars}",
     ":com.android.net.flags-aconfig-java{.generated_srcjars}",
+    ":device_policy_aconfig_flags_lib{.generated_srcjars}",
 ]
 
 filegroup {
@@ -149,6 +152,11 @@
 java_aconfig_library {
     name: "android.nfc.flags-aconfig-java",
     aconfig_declarations: "android.nfc.flags-aconfig",
+    min_sdk_version: "VanillaIceCream",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.nfcservices",
+    ],
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
@@ -286,6 +294,12 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+rust_aconfig_library {
+    name: "libandroid_security_flags_rust",
+    crate_name: "android_security_flags",
+    aconfig_declarations: "android.security.flags-aconfig",
+}
+
 // Package Manager
 aconfig_declarations {
     name: "android.content.pm.flags-aconfig",
@@ -306,6 +320,19 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+// Resources
+aconfig_declarations {
+    name: "android.content.res.flags-aconfig",
+    package: "android.content.res",
+    srcs: ["core/java/android/content/res/*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.content.res.flags-aconfig-java",
+    aconfig_declarations: "android.content.res.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Media BetterTogether
 aconfig_declarations {
     name: "com.android.media.flags.bettertogether-aconfig",
@@ -509,3 +536,36 @@
     aconfig_declarations: "com.android.net.flags-aconfig",
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
+
+// DevicePolicy
+aconfig_declarations {
+    name: "device_policy_aconfig_flags",
+    package: "android.app.admin.flags",
+    srcs: [
+        "core/java/android/app/admin/flags/flags.aconfig",
+    ],
+}
+
+java_aconfig_library {
+    name: "device_policy_aconfig_flags_lib",
+    aconfig_declarations: "device_policy_aconfig_flags",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+cc_aconfig_library {
+    name: "device_policy_aconfig_flags_c_lib",
+    aconfig_declarations: "device_policy_aconfig_flags",
+}
+
+// Notifications
+aconfig_declarations {
+    name: "android.service.notification.flags-aconfig",
+    package: "android.service.notification",
+    srcs: ["core/java/android/service/notification/flags.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.service.notification.flags-aconfig-java",
+    aconfig_declarations: "android.service.notification.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp
index 45bb161..e7adf20 100644
--- a/ProtoLibraries.bp
+++ b/ProtoLibraries.bp
@@ -77,6 +77,42 @@
     output_extension: "proto.h",
 }
 
+// ====  nfc framework java library  ==============================
+gensrcs {
+    name: "framework-nfc-javastream-protos",
+
+    tools: [
+        "aprotoc",
+        "protoc-gen-javastream",
+        "soong_zip",
+    ],
+
+    cmd: "mkdir -p $(genDir)/$(in) " +
+        "&& $(location aprotoc) " +
+        "  --plugin=$(location protoc-gen-javastream) " +
+        "  --javastream_out=$(genDir)/$(in) " +
+        "  -Iexternal/protobuf/src " +
+        "  -I . " +
+        "  $(in) " +
+        "&& $(location soong_zip) -jar -o $(out) -C $(genDir)/$(in) -D $(genDir)/$(in)",
+
+    srcs: [
+        "core/proto/android/app/pendingintent.proto",
+        "core/proto/android/content/component_name.proto",
+        "core/proto/android/content/intent.proto",
+        "core/proto/android/nfc/*.proto",
+        "core/proto/android/os/patternmatcher.proto",
+        "core/proto/android/os/persistablebundle.proto",
+        "core/proto/android/privacy.proto",
+    ],
+
+    data: [
+        ":libprotobuf-internal-protos",
+    ],
+
+    output_extension: "srcjar",
+}
+
 // ====  java proto host library  ==============================
 java_library_host {
     name: "platformprotos",
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index d48d84b..1fdf906 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -75,6 +75,7 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
+import java.util.regex.Pattern;
 
 /**
  * Maintains the master list of jobs that the job scheduler is tracking. These jobs are compared by
@@ -99,6 +100,8 @@
     private static final long SCHEDULED_JOB_HIGH_WATER_MARK_PERIOD_MS = 30 * 60_000L;
     @VisibleForTesting
     static final String JOB_FILE_SPLIT_PREFIX = "jobs_";
+    private static final Pattern SPLIT_FILE_PATTERN =
+            Pattern.compile("^" + JOB_FILE_SPLIT_PREFIX + "\\d+.xml$");
     private static final int ALL_UIDS = -1;
     @VisibleForTesting
     static final int INVALID_UID = -2;
@@ -1121,6 +1124,11 @@
             int numDuplicates = 0;
             synchronized (mLock) {
                 for (File file : files) {
+                    if (!file.getName().equals("jobs.xml")
+                            && !SPLIT_FILE_PATTERN.matcher(file.getName()).matches()) {
+                        // Skip temporary or other files.
+                        continue;
+                    }
                     final AtomicFile aFile = createJobFile(file);
                     try (FileInputStream fis = aFile.openRead()) {
                         jobs = readJobMapImpl(fis, rtcGood, nowElapsed);
diff --git a/api/ApiDocs.bp b/api/ApiDocs.bp
index e162100..e086bfe 100644
--- a/api/ApiDocs.bp
+++ b/api/ApiDocs.bp
@@ -139,9 +139,22 @@
 // using droiddoc
 /////////////////////////////////////////////////////////////////////
 
-framework_docs_only_args = " -android -manifest $(location :frameworks-base-core-AndroidManifest.xml) " +
+// 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 -hide 111 -hide 113 -hide 125 -hide 126 -hide 127 -hide 128 " +
+    "-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 " +
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index 7e41660..7e78185 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -739,7 +739,9 @@
         "android_stubs_current_contributions",
         "android_system_stubs_current_contributions",
         "android_test_frameworks_core_stubs_current_contributions",
-        "stub-annotation-defaults",
+    ],
+    libs: [
+        "stub-annotations",
     ],
     api_contributions: [
         "api-stubs-docs-non-updatable.api.contribution",
diff --git a/api/javadoc-lint-baseline b/api/javadoc-lint-baseline
index d9e72b8..29a8dfa 100644
--- a/api/javadoc-lint-baseline
+++ b/api/javadoc-lint-baseline
@@ -1,58 +1,4 @@
-android/adservices/ondevicepersonalization/DownloadCompletedInput.java:22: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onDownloadCompleted() IsolatedWorker#onDownloadCompleted()" in android.adservices.ondevicepersonalization.DownloadCompletedInput [101]
-android/adservices/ondevicepersonalization/DownloadCompletedOutput.java:21: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onDownloadCompleted() IsolatedWorker#onDownloadCompleted()" in android.adservices.ondevicepersonalization.DownloadCompletedOutput [101]
-android/adservices/ondevicepersonalization/EventLogRecord.java:13: lint: Unresolved link/see tag "RequestRecordRecord" in android.adservices.ondevicepersonalization.EventLogRecord [101]
-android/adservices/ondevicepersonalization/EventUrlProvider.java:43: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onEvent IsolatedWorker#onEvent" in android.adservices.ondevicepersonalization.EventUrlProvider [101]
-android/adservices/ondevicepersonalization/ExecuteInput.java:22: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.ExecuteInput [101]
-android/adservices/ondevicepersonalization/ExecuteOutput.java:20: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.ExecuteOutput [101]
-android/adservices/ondevicepersonalization/ExecuteOutput.java:20: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.OnDevicePersonalizationManager#execute() OnDevicePersonalizationManager#execute()" in android.adservices.ondevicepersonalization.ExecuteOutput [101]
-android/adservices/ondevicepersonalization/ExecuteOutput.java:31: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.ExecuteOutput [101]
-android/adservices/ondevicepersonalization/ExecuteOutput.java:93: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.ExecuteOutput.Builder [101]
-android/adservices/ondevicepersonalization/IsolatedService.java:18: lint: Unresolved link/see tag "SurfaceView" in android.adservices.ondevicepersonalization.IsolatedService [101]
-android/adservices/ondevicepersonalization/IsolatedService.java:18: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.OnDevicePersonalizationManager#execute" in android.adservices.ondevicepersonalization.IsolatedService [101]
-android/adservices/ondevicepersonalization/IsolatedService.java:119: lint: Unresolved link/see tag "IsolatedCmputationCallback#onWebViewEvent()" in android.adservices.ondevicepersonalization.IsolatedService [101]
-android/adservices/ondevicepersonalization/IsolatedService.java:119: lint: Unresolved link/see tag "IsolatedCmputationCallback#onEvent()" in android.adservices.ondevicepersonalization.IsolatedService [101]
-android/adservices/ondevicepersonalization/IsolatedService.java:119: lint: Unresolved link/see tag "WebView" in android.adservices.ondevicepersonalization.IsolatedService [101]
-android/adservices/ondevicepersonalization/IsolatedWorker.java:9: lint: Unresolved link/see tag "RunTimeException" in android.adservices.ondevicepersonalization.IsolatedWorker [101]
-android/adservices/ondevicepersonalization/IsolatedWorker.java:24: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.OnDevicePersonalizationManager#execute" in android.adservices.ondevicepersonalization.IsolatedWorker [101]
-android/adservices/ondevicepersonalization/IsolatedWorker.java:57: lint: Unresolved link/see tag "#onExecute()" in android.adservices.ondevicepersonalization.IsolatedWorker [101]
-android/adservices/ondevicepersonalization/IsolatedWorker.java:74: lint: Unresolved link/see tag "#onRender()" in android.adservices.ondevicepersonalization.IsolatedWorker [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:-11: lint: Unresolved link/see tag "requestSurfacePackage" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:11: lint: Unresolved link/see tag "SurfaceView" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:11: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedService#onExecute() IsolatedService#onExecute()" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:19: lint: Unresolved link/see tag "SurfaceView" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:54: lint: Unresolved link/see tag "SurfaceView" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:54: lint: Unresolved link/see tag "SurfaceView#getHostToken()" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:54: lint: Unresolved link/see tag "execute" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:60: lint: Unresolved link/see tag "#execute()" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:60: lint: Unresolved link/see tag "SurfacePackage" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:60: lint: Unresolved link/see tag "SurfaceView" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:60: lint: Unresolved link/see tag "View" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:64: lint: Unresolved link/see tag "SurfacePackage" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:69: lint: Unresolved link/see tag "SurfacePackage" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:70: lint: Unresolved link/see tag "SurfacePackage" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/RenderInput.java:21: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onRender() IsolatedWorker#onRender()" in android.adservices.ondevicepersonalization.RenderInput [101]
-android/adservices/ondevicepersonalization/RenderInput.java:53: lint: Unresolved link/see tag "onExecute" in android.adservices.ondevicepersonalization.RenderInput [101]
-android/adservices/ondevicepersonalization/RenderOutput.java:20: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.RenderOutput [101]
-android/adservices/ondevicepersonalization/RenderOutput.java:20: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.OnDevicePersonalizationManager#requestSurfacePackage() OnDevicePersonalizationManager#requestSurfacePackage()" in android.adservices.ondevicepersonalization.RenderOutput [101]
-android/adservices/ondevicepersonalization/RenderOutput.java:31: lint: Unresolved link/see tag "getTemplateId()" in android.adservices.ondevicepersonalization.RenderOutput [101]
-android/adservices/ondevicepersonalization/RenderOutput.java:31: lint: Unresolved link/see tag "getTemplateParams" in android.adservices.ondevicepersonalization.RenderOutput [101]
-android/adservices/ondevicepersonalization/RenderOutput.java:41: lint: Unresolved link/see tag "getContent()" in android.adservices.ondevicepersonalization.RenderOutput [101]
-android/adservices/ondevicepersonalization/RenderOutput.java:52: lint: Unresolved link/see tag "getTemplateId()" in android.adservices.ondevicepersonalization.RenderOutput [101]
-android/adservices/ondevicepersonalization/RenderOutput.java:102: lint: Unresolved link/see tag "getTemplateId()" in android.adservices.ondevicepersonalization.RenderOutput.Builder [101]
-android/adservices/ondevicepersonalization/RenderOutput.java:102: lint: Unresolved link/see tag "getTemplateParams" in android.adservices.ondevicepersonalization.RenderOutput.Builder [101]
-android/adservices/ondevicepersonalization/RenderOutput.java:114: lint: Unresolved link/see tag "getContent()" in android.adservices.ondevicepersonalization.RenderOutput.Builder [101]
-android/adservices/ondevicepersonalization/RenderOutput.java:127: lint: Unresolved link/see tag "getTemplateId()" in android.adservices.ondevicepersonalization.RenderOutput.Builder [101]
-android/adservices/ondevicepersonalization/RenderingConfig.java:20: lint: Unresolved link/see tag "View" in android.adservices.ondevicepersonalization.RenderingConfig [101]
-android/adservices/ondevicepersonalization/RenderingConfig.java:20: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.RenderingConfig [101]
-android/adservices/ondevicepersonalization/RenderingConfig.java:20: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onRender() IsolatedWorker#onRender()" in android.adservices.ondevicepersonalization.RenderingConfig [101]
-android/adservices/ondevicepersonalization/RenderingConfig.java:33: lint: Unresolved link/see tag "IsolatedSurface#getRemoteData" in android.adservices.ondevicepersonalization.RenderingConfig [101]
-android/adservices/ondevicepersonalization/RenderingConfig.java:85: lint: Unresolved link/see tag "IsolatedSurface#getRemoteData" in android.adservices.ondevicepersonalization.RenderingConfig.Builder [101]
-android/adservices/ondevicepersonalization/RequestLogRecord.java:19: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.RequestLogRecord [101]
-android/adservices/ondevicepersonalization/SurfacePackageToken.java:20: lint: Unresolved link/see tag "SurfaceView" in android.adservices.ondevicepersonalization.SurfacePackageToken [101]
-android/adservices/ondevicepersonalization/WebViewEventInput.java:21: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onWebViewEvent() IsolatedWorker#onWebViewEvent()" in android.adservices.ondevicepersonalization.WebViewEventInput [101]
-android/adservices/ondevicepersonalization/WebViewEventInput.java:30: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.WebViewEventInput [101]
-android/adservices/ondevicepersonalization/WebViewEventInput.java:41: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.EventUrlProvider#createEventTrackingUrlWithResponse() EventUrlProvider#createEventTrackingUrlWithResponse()" in android.adservices.ondevicepersonalization.WebViewEventInput [101]
-android/adservices/ondevicepersonalization/WebViewEventOutput.java:21: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onWebViewEvent() IsolatedWorker#onWebViewEvent()" in android.adservices.ondevicepersonalization.WebViewEventOutput [101]
+// b/305195721
 android/app/admin/DevicePolicyManager.java:2670: lint: Unresolved link/see tag "android.os.UserManager#DISALLOW_CAMERA UserManager#DISALLOW_CAMERA" in android.app.admin.DevicePolicyManager [101]
 android/app/admin/DevicePolicyManager.java:7257: lint: Unresolved link/see tag "android.app.admin.DevicePolicyIdentifiers#USB_DATA_SIGNALING_POLICY DevicePolicyIdentifiers#USB_DATA_SIGNALING_POLICY" in android.app.admin.DevicePolicyManager [101]
 android/app/admin/DevicePolicyManager.java:7425: lint: Unresolved link/see tag "ACTION_DEVICE_FINANCING_STATE_CHANGED" in android.app.admin.DevicePolicyManager [101]
@@ -61,6 +7,8 @@
 android/app/admin/DevicePolicyManager.java:8860: lint: Unresolved link/see tag "android.app.admin.DevicePolicyResources.Drawables DevicePolicyResources.Drawables" in android.app.admin.DevicePolicyManager [101]
 android/app/admin/DevicePolicyManager.java:8860: lint: Unresolved link/see tag "android.app.admin.DevicePolicyResources.Strings DevicePolicyResources.Strings" in android.app.admin.DevicePolicyManager [101]
 android/app/admin/DevicePolicyResourcesManager.java:179: lint: Unresolved link/see tag "android.app.admin.DevicePolicyResources.Strings DevicePolicyResources.Strings" in android.app.admin.DevicePolicyResourcesManager [101]
+
+// b/303477132
 android/app/appsearch/AppSearchSchema.java:402: lint: Unresolved link/see tag "#getIndexableNestedProperties()" in android.app.appsearch.AppSearchSchema.DocumentPropertyConfig.Builder [101]
 android/app/appsearch/AppSearchSession.java:55: lint: Unresolved link/see tag "Features#LIST_FILTER_QUERY_LANGUAGE" in android.app.appsearch.AppSearchSession [101]
 android/app/appsearch/AppSearchSession.java:55: lint: Unresolved link/see tag "Features#NUMERIC_SEARCH" in android.app.appsearch.AppSearchSession [101]
@@ -73,6 +21,8 @@
 android/app/appsearch/SearchSpec.java:913: lint: Unresolved link/see tag "Features#NUMERIC_SEARCH" in android.app.appsearch.SearchSpec.Builder [101]
 android/app/appsearch/SearchSpec.java:925: lint: Unresolved link/see tag "Features#VERBATIM_SEARCH" in android.app.appsearch.SearchSpec.Builder [101]
 android/app/appsearch/SearchSpec.java:929: lint: Unresolved link/see tag "Features#LIST_FILTER_QUERY_LANGUAGE" in android.app.appsearch.SearchSpec.Builder [101]
+
+// b/303582215
 android/hardware/camera2/CameraCharacteristics.java:2169: lint: Unresolved link/see tag "android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations guideline" in android.hardware.camera2.CameraCharacteristics [101]
 android/hardware/camera2/CameraCharacteristics.java:2344: lint: Unresolved link/see tag "android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations guideline" in android.hardware.camera2.CameraCharacteristics [101]
 android/hardware/camera2/CameraCharacteristics.java:2344: lint: Unresolved link/see tag "android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations tables" in android.hardware.camera2.CameraCharacteristics [101]
@@ -98,77 +48,37 @@
 android/hardware/camera2/CaptureRequest.java:1501: lint: Unresolved link/see tag "SessionConfiguration#setSessionParameters" in android.hardware.camera2.CaptureRequest [101]
 android/hardware/camera2/CaptureResult.java:923: lint: Unresolved link/see tag "SessionConfiguration#setSessionParameters" in android.hardware.camera2.CaptureResult [101]
 android/hardware/camera2/CaptureResult.java:2337: lint: Unresolved link/see tag "SessionConfiguration#setSessionParameters" in android.hardware.camera2.CaptureResult [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ALARM AttributeSdkUsage#USAGE_ALARM" in android.media.AudioAttributes.Builder [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ASSISTANCE_ACCESSIBILITY AttributeSdkUsage#USAGE_ASSISTANCE_ACCESSIBILITY" in android.media.AudioAttributes.Builder [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE AttributeSdkUsage#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE" in android.media.AudioAttributes.Builder [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ASSISTANCE_SONIFICATION AttributeSdkUsage#USAGE_ASSISTANCE_SONIFICATION" in android.media.AudioAttributes.Builder [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ASSISTANT AttributeSdkUsage#USAGE_ASSISTANT" in android.media.AudioAttributes.Builder [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_GAME AttributeSdkUsage#USAGE_GAME" in android.media.AudioAttributes.Builder [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_MEDIA AttributeSdkUsage#USAGE_MEDIA" in android.media.AudioAttributes.Builder [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_NOTIFICATION_EVENT AttributeSdkUsage#USAGE_NOTIFICATION_EVENT" in android.media.AudioAttributes.Builder [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_NOTIFICATION_RINGTONE AttributeSdkUsage#USAGE_NOTIFICATION_RINGTONE" in android.media.AudioAttributes.Builder [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_UNKNOWN AttributeSdkUsage#USAGE_UNKNOWN" in android.media.AudioAttributes.Builder [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_VOICE_COMMUNICATION AttributeSdkUsage#USAGE_VOICE_COMMUNICATION" in android.media.AudioAttributes.Builder [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_VOICE_COMMUNICATION_SIGNALLING AttributeSdkUsage#USAGE_VOICE_COMMUNICATION_SIGNALLING" in android.media.AudioAttributes.Builder [101]
-android/media/AudioFormat.java:963: lint: Unresolved link/see tag "android.media.AudioSystem#OUT_CHANNEL_COUNT_MAX AudioSystem#OUT_CHANNEL_COUNT_MAX" in android.media.AudioFormat.Builder [101]
-android/media/AudioManager.java:275: lint: Unresolved link/see tag "android.media.audiopolicy.AudioVolumeGroup" in android.media.AudioManager [101]
-android/media/AudioManager.java:287: lint: Unresolved link/see tag "android.media.audiopolicy.AudioVolumeGroup" in android.media.AudioManager [101]
-android/media/AudioManager.java:311: lint: Unresolved link/see tag "android.media.audiopolicy.AudioVolumeGroup" in android.media.AudioManager [101]
-android/media/AudioManager.java:313: lint: Unresolved link/see tag "android.media.audiopolicy.AudioVolumeGroup" in android.media.AudioManager [101]
-android/media/AudioMetadata.java:118: lint: Unresolved link/see tag "android.media.AudioPresentation.ContentClassifier One of {@link android.media.AudioPresentation#CONTENT_UNKNOWN AudioPresentation#CONTENT_UNKNOWN},     {@link android.media.AudioPresentation#CONTENT_MAIN AudioPresentation#CONTENT_MAIN},     {@link android.media.AudioPresentation#CONTENT_MUSIC_AND_EFFECTS AudioPresentation#CONTENT_MUSIC_AND_EFFECTS},     {@link android.media.AudioPresentation#CONTENT_VISUALLY_IMPAIRED AudioPresentation#CONTENT_VISUALLY_IMPAIRED},     {@link android.media.AudioPresentation#CONTENT_HEARING_IMPAIRED AudioPresentation#CONTENT_HEARING_IMPAIRED},     {@link android.media.AudioPresentation#CONTENT_DIALOG AudioPresentation#CONTENT_DIALOG},     {@link android.media.AudioPresentation#CONTENT_COMMENTARY AudioPresentation#CONTENT_COMMENTARY},     {@link android.media.AudioPresentation#CONTENT_EMERGENCY AudioPresentation#CONTENT_EMERGENCY},     {@link android.media.AudioPresentation#CONTENT_VOICEOVER AudioPresentation#CONTENT_VOICEOVER}." in android.media.AudioMetadata.Format [101]
-android/media/tv/SectionRequest.java:44: lint: Unresolved link/see tag "android.media.tv.BroadcastInfoRequest.RequestOption BroadcastInfoRequest.RequestOption" in android.media.tv.SectionRequest [101]
-android/media/tv/SectionResponse.java:39: lint: Unresolved link/see tag "android.media.tv.BroadcastInfoRequest.RequestOption BroadcastInfoRequest.RequestOption" in android.media.tv.SectionResponse [101]
-android/media/tv/TableRequest.java:48: lint: Unresolved link/see tag "android.media.tv.BroadcastInfoRequest.RequestOption BroadcastInfoRequest.RequestOption" in android.media.tv.TableRequest [101]
-android/media/tv/TableResponse.java:82: lint: Unresolved link/see tag "android.media.tv.BroadcastInfoRequest.RequestOption BroadcastInfoRequest.RequestOption" in android.media.tv.TableResponse [101]
-android/net/EthernetNetworkSpecifier.java:21: lint: Unresolved link/see tag "android.net.EthernetManager" in android.net.EthernetNetworkSpecifier [101]
-android/net/eap/EapSessionConfig.java:120: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.Builder [101]
-android/net/eap/EapSessionConfig.java:135: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.Builder [101]
-android/net/eap/EapSessionConfig.java:148: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.Builder [101]
-android/net/eap/EapSessionConfig.java:161: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.Builder [101]
-android/net/eap/EapSessionConfig.java:288: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.EapAkaConfig [101]
-android/net/eap/EapSessionConfig.java:390: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.EapAkaPrimeConfig [101]
-android/net/eap/EapSessionConfig.java:587: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.EapSimConfig [101]
-android/net/wifi/MloLink.java:32: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_24_GHZ WifiScanner#WIFI_BAND_24_GHZ" in android.net.wifi.MloLink [101]
-android/net/wifi/MloLink.java:32: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_5_GHZ WifiScanner#WIFI_BAND_5_GHZ" in android.net.wifi.MloLink [101]
-android/net/wifi/MloLink.java:32: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_6_GHZ WifiScanner#WIFI_BAND_6_GHZ" in android.net.wifi.MloLink [101]
-android/net/wifi/MloLink.java:32: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_UNSPECIFIED WifiScanner#WIFI_BAND_UNSPECIFIED" in android.net.wifi.MloLink [101]
-android/net/wifi/SoftApConfiguration.java:9: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder SoftApConfiguration.Builder" in android.net.wifi.SoftApConfiguration [101]
-android/net/wifi/SoftApConfiguration.java:66: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setSsid(java.lang.String) Builder#setSsid(String)" in android.net.wifi.SoftApConfiguration [101]
-android/net/wifi/SoftApConfiguration.java:85: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setWifiSsid(android.net.wifi.WifiSsid) Builder#setWifiSsid(WifiSsid)" in android.net.wifi.SoftApConfiguration [101]
-android/net/wifi/SoftApConfiguration.java:96: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setBssid(android.net.MacAddress) Builder#setBssid(MacAddress)" in android.net.wifi.SoftApConfiguration [101]
-android/net/wifi/SoftApConfiguration.java:107: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setPassphrase(java.lang.String,int) Builder#setPassphrase(String, int)" in android.net.wifi.SoftApConfiguration [101]
-android/net/wifi/SoftApConfiguration.java:118: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setHiddenSsid(boolean) Builder#setHiddenSsid(boolean)" in android.net.wifi.SoftApConfiguration [101]
-android/net/wifi/WifiManager.java:764: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setBands(int[]) SoftApConfiguration.Builder#setBands(int[])" in android.net.wifi.WifiManager [101]
-android/net/wifi/WifiManager.java:764: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setChannels(android.util.SparseIntArray) SoftApConfiguration.Builder#setChannels(android.util.SparseIntArray)" in android.net.wifi.WifiManager [101]
-android/net/wifi/WifiManager.java:779: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setBands(int[]) SoftApConfiguration.Builder#setBands(int[])" in android.net.wifi.WifiManager [101]
-android/net/wifi/WifiManager.java:779: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setChannels(android.util.SparseIntArray) SoftApConfiguration.Builder#setChannels(android.util.SparseIntArray)" in android.net.wifi.WifiManager [101]
-android/net/wifi/WifiManager.java:2466: lint: Unresolved link/see tag "TelephonyManager#hasCarrierPrivileges()." in android.net.wifi.WifiManager [101]
-android/net/wifi/aware/PublishConfig.java:50: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_24_GHZ WifiScanner#WIFI_BAND_24_GHZ" in android.net.wifi.aware.PublishConfig [101]
-android/net/wifi/aware/PublishConfig.java:50: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_5_GHZ WifiScanner#WIFI_BAND_5_GHZ" in android.net.wifi.aware.PublishConfig [101]
-android/net/wifi/aware/PublishConfig.java:249: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_24_GHZ WifiScanner#WIFI_BAND_24_GHZ" in android.net.wifi.aware.PublishConfig.Builder [101]
-android/net/wifi/aware/PublishConfig.java:249: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_5_GHZ WifiScanner#WIFI_BAND_5_GHZ" in android.net.wifi.aware.PublishConfig.Builder [101]
-android/net/wifi/aware/SubscribeConfig.java:51: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_24_GHZ WifiScanner#WIFI_BAND_24_GHZ" in android.net.wifi.aware.SubscribeConfig [101]
-android/net/wifi/aware/SubscribeConfig.java:51: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_5_GHZ WifiScanner#WIFI_BAND_5_GHZ" in android.net.wifi.aware.SubscribeConfig [101]
-android/net/wifi/aware/SubscribeConfig.java:276: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_24_GHZ WifiScanner#WIFI_BAND_24_GHZ" in android.net.wifi.aware.SubscribeConfig.Builder [101]
-android/net/wifi/aware/SubscribeConfig.java:276: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_5_GHZ WifiScanner#WIFI_BAND_5_GHZ" in android.net.wifi.aware.SubscribeConfig.Builder [101]
 
-android/net/wifi/SoftApConfiguration.java:173: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setShutdownTimeoutMillis(long)" in android.net.wifi.SoftApConfiguration [101]
-android/os/UserManager.java:2384: lint: Unresolved link/see tag "android.annotation.UserHandleAware @UserHandleAware" in android.os.UserManager [101]
+// These are javadoc errors for @ChangeId constants, which are problematic to generate documentation
+// for. They're not necessarily errors in the docs themselves but are also a limitation in the tool.
+// Regardless, the docs currently generated for them is not good, but it is also not used directly
+// in production at the moment.
+// The main limitation is that all references must be fully qualified in order to resolve properly
+// (aside from the normal limitatinos of only being able to @link public APIs).
+// See the CompatInfo.java source file in doclava for more information.
+android/net/wifi/SoftApConfiguration.java:171: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setShutdownTimeoutMillis(long)" in android.net.wifi.SoftApConfiguration [101]
 android/os/UserManager.java:2384: lint: Unresolved link/see tag "android.annotation.UserHandleAware#enabledSinceTargetSdkVersion" in android.os.UserManager [101]
-android/service/voice/AlwaysOnHotwordDetector.java:269: lint: Unresolved link/see tag "#initialize( PersistableBundle, SharedMemory, SoundTrigger.ModuleProperties)" in android.service.voice.AlwaysOnHotwordDetector [101]
-android/service/voice/AlwaysOnHotwordDetector.java:269: lint: Unresolved link/see tag "STATE_HARDWARE_UNAVAILABLE" in android.service.voice.AlwaysOnHotwordDetector [101]
-android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "#STATE_ERROR" in android [101]
+android/os/UserManager.java:2384: lint: Unresolved link/see tag "android.annotation.UserHandleAware @UserHandleAware" in android.os.UserManager [101]
+android/service/voice/AlwaysOnHotwordDetector.java:269: lint: Unresolved link/see tag "#initialize( PersistableBundle, SharedMemory, SoundTrigger.ModuleProperties)" in android [101]
+android/service/voice/AlwaysOnHotwordDetector.java:269: lint: Unresolved link/see tag "STATE_HARDWARE_UNAVAILABLE" in android [101]
+android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "android.service.voice.AlwaysOnHotwordDetector.Callback#onFailure" in android [101]
+android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "android.service.voice.AlwaysOnHotwordDetector.Callback#onUnknownFailure" in android [101]
+android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "android.service.voice.AlwaysOnHotwordDetector#STATE_ERROR" in android [101]
 android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "Callback#onFailure" in android [101]
 android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "Callback#onUnknownFailure" in android [101]
-com/android/internal/policy/PhoneWindow.java:172: lint: Unresolved link/see tag "Build.VERSION_CODES#VANILLA_ICE_CREAM" in com.android.internal.policy.PhoneWindow [101]
-
+android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "#STATE_ERROR" in android [101]
+com/android/internal/policy/PhoneWindow.java:172: lint: Unresolved link/see tag "Build.VERSION_CODES#VANILLA_ICE_CREAM" in android [101]
 com/android/server/broadcastradio/aidl/ConversionUtils.java:70: lint: Unresolved link/see tag "IdentifierType#DAB_SID_EXT" in android [101]
 com/android/server/broadcastradio/aidl/ConversionUtils.java:70: lint: Unresolved link/see tag "ProgramSelector#IDENTIFIER_TYPE_DAB_DMB_SID_EXT" in android [101]
 com/android/server/broadcastradio/aidl/ConversionUtils.java:70: lint: Unresolved link/see tag "RadioTuner" in android [101]
+com/android/server/broadcastradio/aidl/ConversionUtils.java:72: lint: Unresolved link/see tag "com.android.server.broadcastradio.aidl.IdentifierType#DAB_SID_EXT" in android [101]
+com/android/server/broadcastradio/aidl/ConversionUtils.java:72: lint: Unresolved link/see tag "com.android.server.broadcastradio.aidl.ProgramSelector#IDENTIFIER_TYPE_DAB_DMB_SID_EXT" in android [101]
+com/android/server/broadcastradio/aidl/ConversionUtils.java:72: lint: Unresolved link/see tag "com.android.server.broadcastradio.aidl.RadioTuner" in android [101]
+com/android/server/devicepolicy/DevicePolicyManagerService.java:861: lint: Unresolved link/see tag "android.security.IKeyChainService#setGrant" in android [101]
 com/android/server/pm/PackageInstallerSession.java:313: lint: Unresolved link/see tag "Build.VERSION_CODES#S API 31" in android [101]
 com/android/server/pm/PackageInstallerSession.java:313: lint: Unresolved link/see tag "PackageInstaller.SessionParams#setRequireUserAction" in android [101]
-com/android/server/pm/PackageInstallerSession.java:327: lint: Unresolved link/see tag "#requestUserPreapproval(PreapprovalDetails, IntentSender)" in android [101]
 com/android/server/pm/PackageInstallerSession.java:327: lint: Unresolved link/see tag "Build.VERSION_CODES#UPSIDE_DOWN_CAKE API 34" in android [101]
 com/android/server/pm/PackageInstallerSession.java:327: lint: Unresolved link/see tag "PackageInstaller.SessionParams#setRequestUpdateOwnership(boolean)" in android [101]
-com/android/server/pm/PackageInstallerSession.java:358: lint: Unresolved link/see tag "IntentSender" in android [101]
-com/android/server/devicepolicy/DevicePolicyManagerService.java:860: lint: Unresolved link/see tag "android.security.IKeyChainService#setGrant" in android [101]
+com/android/server/pm/PackageInstallerSession.java:327: lint: Unresolved link/see tag "#requestUserPreapproval(PreapprovalDetails, IntentSender)" in android [101]
+com/android/server/pm/PackageInstallerSession.java:330: lint: Unresolved link/see tag "com.android.android.server.pm#requestUserPreapproval(PreapprovalDetails, IntentSender)" in android [101]
+com/android/server/pm/PackageInstallerSession.java:358: lint: Unresolved link/see tag "IntentSender" in android [101]
\ No newline at end of file
diff --git a/core/api/current.txt b/core/api/current.txt
index f6564ec..7d5417d 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -668,6 +668,7 @@
     field public static final int debuggable = 16842767; // 0x101000f
     field public static final int defaultFocusHighlightEnabled = 16844130; // 0x1010562
     field public static final int defaultHeight = 16844021; // 0x10104f5
+    field @FlaggedApi("android.content.res.default_locale") public static final int defaultLocale;
     field public static final int defaultToDeviceProtectedStorage = 16844036; // 0x1010504
     field public static final int defaultValue = 16843245; // 0x10101ed
     field public static final int defaultWidth = 16844020; // 0x10104f4
@@ -6188,6 +6189,7 @@
     ctor public LocaleConfig(@NonNull android.os.LocaleList);
     method public int describeContents();
     method @NonNull public static android.app.LocaleConfig fromContextIgnoringOverride(@NonNull android.content.Context);
+    method @FlaggedApi("android.content.res.default_locale") @Nullable public java.util.Locale getDefaultLocale();
     method public int getStatus();
     method @Nullable public android.os.LocaleList getSupportedLocales();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -9530,7 +9532,8 @@
     method @Nullable public CharSequence getDisplayName();
     method public int getId();
     method public int getSystemDataSyncFlags();
-    method @Nullable public String getTag();
+    method @FlaggedApi("android.companion.association_tag") @Nullable public String getTag();
+    method public boolean isSelfManaged();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.companion.AssociationInfo> CREATOR;
   }
@@ -9600,7 +9603,7 @@
     method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public void attachSystemDataTransport(int, @NonNull java.io.InputStream, @NonNull java.io.OutputStream) throws android.companion.DeviceNotAssociatedException;
     method @Nullable public android.content.IntentSender buildAssociationCancellationIntent();
     method @Nullable public android.content.IntentSender buildPermissionTransferUserConsentIntent(int) throws android.companion.DeviceNotAssociatedException;
-    method public void clearAssociationTag(int);
+    method @FlaggedApi("android.companion.association_tag") public void clearAssociationTag(int);
     method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public void detachSystemDataTransport(int) throws android.companion.DeviceNotAssociatedException;
     method public void disableSystemDataSyncForTypes(int, int);
     method @Deprecated public void disassociate(@NonNull String);
@@ -9610,7 +9613,7 @@
     method @NonNull public java.util.List<android.companion.AssociationInfo> getMyAssociations();
     method @Deprecated public boolean hasNotificationAccess(android.content.ComponentName);
     method public void requestNotificationAccess(android.content.ComponentName);
-    method public void setAssociationTag(int, @NonNull String);
+    method @FlaggedApi("android.companion.association_tag") public void setAssociationTag(int, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
     method public void startSystemDataTransfer(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.companion.CompanionException>) throws android.companion.DeviceNotAssociatedException;
     method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
@@ -10571,7 +10574,7 @@
   public final class ContextParams {
     method @Nullable public String getAttributionTag();
     method @Nullable public android.content.AttributionSource getNextAttributionSource();
-    method @NonNull public boolean shouldRegisterAttributionSource();
+    method @FlaggedApi("android.permission.flags.should_register_attribution_source") @NonNull public boolean shouldRegisterAttributionSource();
   }
 
   public static final class ContextParams.Builder {
@@ -10580,7 +10583,7 @@
     method @NonNull public android.content.ContextParams build();
     method @NonNull public android.content.ContextParams.Builder setAttributionTag(@Nullable String);
     method @NonNull public android.content.ContextParams.Builder setNextAttributionSource(@Nullable android.content.AttributionSource);
-    method @NonNull public android.content.ContextParams.Builder setShouldRegisterAttributionSource(boolean);
+    method @FlaggedApi("android.permission.flags.should_register_attribution_source") @NonNull public android.content.ContextParams.Builder setShouldRegisterAttributionSource(boolean);
   }
 
   public class ContextWrapper extends android.content.Context {
@@ -11719,7 +11722,7 @@
     method @NonNull public void setResourceValue(@NonNull String, @IntRange(from=android.util.TypedValue.TYPE_FIRST_INT, to=android.util.TypedValue.TYPE_LAST_INT) int, int, @Nullable String);
     method @NonNull public void setResourceValue(@NonNull String, int, @NonNull String, @Nullable String);
     method @NonNull public void setResourceValue(@NonNull String, @NonNull android.os.ParcelFileDescriptor, @Nullable String);
-    method @NonNull public void setResourceValue(@NonNull String, @NonNull android.content.res.AssetFileDescriptor, @Nullable String);
+    method @FlaggedApi("android.content.res.asset_file_descriptor_frro") @NonNull public void setResourceValue(@NonNull String, @NonNull android.content.res.AssetFileDescriptor, @Nullable String);
     method public void setTargetOverlayable(@Nullable String);
   }
 
@@ -12675,6 +12678,7 @@
     method public boolean isDeviceUpgrading();
     method public abstract boolean isInstantApp();
     method public abstract boolean isInstantApp(@NonNull String);
+    method @FlaggedApi("android.content.pm.quarantined_enabled") public boolean isPackageQuarantined(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
     method @FlaggedApi("android.content.pm.stay_stopped") public boolean isPackageStopped(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public boolean isPackageSuspended(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public boolean isPackageSuspended();
@@ -12913,6 +12917,7 @@
     field public static final int MATCH_DIRECT_BOOT_UNAWARE = 262144; // 0x40000
     field public static final int MATCH_DISABLED_COMPONENTS = 512; // 0x200
     field public static final int MATCH_DISABLED_UNTIL_USED_COMPONENTS = 32768; // 0x8000
+    field @FlaggedApi("android.content.pm.quarantined_enabled") public static final long MATCH_QUARANTINED_COMPONENTS = 4294967296L; // 0x100000000L
     field public static final int MATCH_SYSTEM_ONLY = 1048576; // 0x100000
     field public static final int MATCH_UNINSTALLED_PACKAGES = 8192; // 0x2000
     field public static final long MAXIMUM_VERIFICATION_TIMEOUT = 3600000L; // 0x36ee80L
@@ -14352,14 +14357,14 @@
   public final class SQLiteDatabase extends android.database.sqlite.SQLiteClosable {
     method public void beginTransaction();
     method public void beginTransactionNonExclusive();
-    method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public void beginTransactionReadOnly();
+    method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public void beginTransactionReadOnly();
     method public void beginTransactionWithListener(@Nullable android.database.sqlite.SQLiteTransactionListener);
     method public void beginTransactionWithListenerNonExclusive(@Nullable android.database.sqlite.SQLiteTransactionListener);
-    method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public void beginTransactionWithListenerReadOnly(@Nullable android.database.sqlite.SQLiteTransactionListener);
+    method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public void beginTransactionWithListenerReadOnly(@Nullable android.database.sqlite.SQLiteTransactionListener);
     method public android.database.sqlite.SQLiteStatement compileStatement(String) throws android.database.SQLException;
     method @NonNull public static android.database.sqlite.SQLiteDatabase create(@Nullable android.database.sqlite.SQLiteDatabase.CursorFactory);
     method @NonNull public static android.database.sqlite.SQLiteDatabase createInMemory(@NonNull android.database.sqlite.SQLiteDatabase.OpenParams);
-    method @FlaggedApi("android.database.sqlite.sqlite_apis_15") @NonNull public android.database.sqlite.SQLiteRawStatement createRawStatement(@NonNull String);
+    method @FlaggedApi("android.database.sqlite.sqlite_apis_35") @NonNull public android.database.sqlite.SQLiteRawStatement createRawStatement(@NonNull String);
     method public int delete(@NonNull String, @Nullable String, @Nullable String[]);
     method public static boolean deleteDatabase(@NonNull java.io.File);
     method public void disableWriteAheadLogging();
@@ -14370,13 +14375,13 @@
     method public void execSQL(@NonNull String, @NonNull Object[]) throws android.database.SQLException;
     method public static String findEditTable(String);
     method public java.util.List<android.util.Pair<java.lang.String,java.lang.String>> getAttachedDbs();
-    method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public long getLastChangedRowCount();
-    method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public long getLastInsertRowId();
+    method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public long getLastChangedRowCount();
+    method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public long getLastInsertRowId();
     method public long getMaximumSize();
     method public long getPageSize();
     method public String getPath();
     method @Deprecated public java.util.Map<java.lang.String,java.lang.String> getSyncedTables();
-    method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public long getTotalChangedRowCount();
+    method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public long getTotalChangedRowCount();
     method public int getVersion();
     method public boolean inTransaction();
     method public long insert(@NonNull String, @Nullable String, @Nullable android.content.ContentValues);
@@ -14598,7 +14603,7 @@
     method public int update(@NonNull android.database.sqlite.SQLiteDatabase, @NonNull android.content.ContentValues, @Nullable String, @Nullable String[]);
   }
 
-  @FlaggedApi("android.database.sqlite.sqlite_apis_15") public final class SQLiteRawStatement implements java.io.Closeable {
+  @FlaggedApi("android.database.sqlite.sqlite_apis_35") public final class SQLiteRawStatement implements java.io.Closeable {
     method public void bindBlob(int, @NonNull byte[]) throws android.database.sqlite.SQLiteException;
     method public void bindBlob(int, @NonNull byte[], int, int) throws android.database.sqlite.SQLiteException;
     method public void bindDouble(int, double) throws android.database.sqlite.SQLiteException;
@@ -18547,12 +18552,12 @@
     ctor @Deprecated public BiometricPrompt.CryptoObject(@NonNull android.security.identity.IdentityCredential);
     ctor public BiometricPrompt.CryptoObject(@NonNull android.security.identity.PresentationSession);
     ctor @FlaggedApi("android.hardware.biometrics.add_key_agreement_crypto_object") public BiometricPrompt.CryptoObject(@NonNull javax.crypto.KeyAgreement);
-    method public javax.crypto.Cipher getCipher();
+    method @Nullable public javax.crypto.Cipher getCipher();
     method @Deprecated @Nullable public android.security.identity.IdentityCredential getIdentityCredential();
     method @FlaggedApi("android.hardware.biometrics.add_key_agreement_crypto_object") @Nullable public javax.crypto.KeyAgreement getKeyAgreement();
-    method public javax.crypto.Mac getMac();
+    method @Nullable public javax.crypto.Mac getMac();
     method @Nullable public android.security.identity.PresentationSession getPresentationSession();
-    method public java.security.Signature getSignature();
+    method @Nullable public java.security.Signature getSignature();
   }
 
 }
@@ -23775,6 +23780,8 @@
     field public static final int TYPE_DOCK = 13; // 0xd
     field public static final int TYPE_GROUP = 2000; // 0x7d0
     field public static final int TYPE_HDMI = 9; // 0x9
+    field @FlaggedApi("com.android.media.flags.enable_audio_policies_device_and_bluetooth_controller") public static final int TYPE_HDMI_ARC = 10; // 0xa
+    field @FlaggedApi("com.android.media.flags.enable_audio_policies_device_and_bluetooth_controller") public static final int TYPE_HDMI_EARC = 29; // 0x1d
     field public static final int TYPE_HEARING_AID = 23; // 0x17
     field public static final int TYPE_REMOTE_AUDIO_VIDEO_RECEIVER = 1003; // 0x3eb
     field public static final int TYPE_REMOTE_CAR = 1008; // 0x3f0
@@ -45702,6 +45709,7 @@
     field public static final int TYPE_IMS = 64; // 0x40
     field public static final int TYPE_MCX = 1024; // 0x400
     field public static final int TYPE_MMS = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int TYPE_RCS = 32768; // 0x8000
     field public static final int TYPE_SUPL = 4; // 0x4
     field public static final int TYPE_VSIM = 4096; // 0x1000
     field public static final int TYPE_XCAP = 2048; // 0x800
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index b5d3ed7..2af3c34 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -6,7 +6,7 @@
     field public static final String CONTROL_AUTOMOTIVE_GNSS = "android.permission.CONTROL_AUTOMOTIVE_GNSS";
     field public static final String GET_INTENT_SENDER_INTENT = "android.permission.GET_INTENT_SENDER_INTENT";
     field public static final String MAKE_UID_VISIBLE = "android.permission.MAKE_UID_VISIBLE";
-    field public static final String USE_COMPANION_TRANSPORTS = "android.permission.USE_COMPANION_TRANSPORTS";
+    field @FlaggedApi("android.companion.flags.companion_transport_apis") public static final String USE_COMPANION_TRANSPORTS = "android.permission.USE_COMPANION_TRANSPORTS";
   }
 
 }
@@ -85,21 +85,21 @@
 package android.companion {
 
   public final class CompanionDeviceManager {
-    method @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void addOnMessageReceivedListener(@NonNull java.util.concurrent.Executor, int, @NonNull android.companion.CompanionDeviceManager.OnMessageReceivedListener);
-    method @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void addOnTransportsChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.CompanionDeviceManager.OnTransportsChangedListener);
-    method @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void removeOnMessageReceivedListener(int, @NonNull android.companion.CompanionDeviceManager.OnMessageReceivedListener);
-    method @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void removeOnTransportsChangedListener(@NonNull android.companion.CompanionDeviceManager.OnTransportsChangedListener);
-    method @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void sendMessage(int, @NonNull byte[], @NonNull int[]);
-    field public static final int MESSAGE_REQUEST_CONTEXT_SYNC = 1667729539; // 0x63678883
-    field public static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 1669491075; // 0x63826983
-    field public static final int MESSAGE_REQUEST_REMOTE_AUTHENTICATION = 1669494629; // 0x63827765
+    method @FlaggedApi("android.companion.companion_transport_apis") @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void addOnMessageReceivedListener(@NonNull java.util.concurrent.Executor, int, @NonNull android.companion.CompanionDeviceManager.OnMessageReceivedListener);
+    method @FlaggedApi("android.companion.companion_transport_apis") @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void addOnTransportsChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.CompanionDeviceManager.OnTransportsChangedListener);
+    method @FlaggedApi("android.companion.companion_transport_apis") @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void removeOnMessageReceivedListener(int, @NonNull android.companion.CompanionDeviceManager.OnMessageReceivedListener);
+    method @FlaggedApi("android.companion.companion_transport_apis") @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void removeOnTransportsChangedListener(@NonNull android.companion.CompanionDeviceManager.OnTransportsChangedListener);
+    method @FlaggedApi("android.companion.companion_transport_apis") @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void sendMessage(int, @NonNull byte[], @NonNull int[]);
+    field @FlaggedApi("android.companion.companion_transport_apis") public static final int MESSAGE_REQUEST_CONTEXT_SYNC = 1667729539; // 0x63678883
+    field @FlaggedApi("android.companion.companion_transport_apis") public static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 1669491075; // 0x63826983
+    field @FlaggedApi("android.companion.companion_transport_apis") public static final int MESSAGE_REQUEST_REMOTE_AUTHENTICATION = 1669494629; // 0x63827765
   }
 
-  public static interface CompanionDeviceManager.OnMessageReceivedListener {
+  @FlaggedApi("android.companion.companion_transport_apis") public static interface CompanionDeviceManager.OnMessageReceivedListener {
     method public void onMessageReceived(int, @NonNull byte[]);
   }
 
-  public static interface CompanionDeviceManager.OnTransportsChangedListener {
+  @FlaggedApi("android.companion.companion_transport_apis") public static interface CompanionDeviceManager.OnTransportsChangedListener {
     method public void onTransportsChanged(@NonNull java.util.List<android.companion.AssociationInfo>);
   }
 
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index c72d09d..ac61107 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -312,7 +312,7 @@
     field public static final String REMOVE_DRM_CERTIFICATES = "android.permission.REMOVE_DRM_CERTIFICATES";
     field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
     field public static final String RENOUNCE_PERMISSIONS = "android.permission.RENOUNCE_PERMISSIONS";
-    field @FlaggedApi("backstage_power.report_usage_stats_permission") public static final String REPORT_USAGE_STATS = "android.permission.REPORT_USAGE_STATS";
+    field @FlaggedApi("android.app.usage.report_usage_stats_permission") public static final String REPORT_USAGE_STATS = "android.permission.REPORT_USAGE_STATS";
     field @Deprecated public static final String REQUEST_NETWORK_SCORES = "android.permission.REQUEST_NETWORK_SCORES";
     field public static final String REQUEST_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE";
     field public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD";
@@ -3149,7 +3149,6 @@
 
   public final class AssociationInfo implements android.os.Parcelable {
     method @NonNull public String getPackageName();
-    method public boolean isSelfManaged();
   }
 
   public final class CompanionDeviceManager {
@@ -9538,7 +9537,7 @@
     method public int getDeviceType();
     method @NonNull public android.os.Bundle getExtras();
     method @NonNull public String getModelName();
-    method public boolean isBatteryCharging();
+    method @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status") public boolean isBatteryCharging();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.sharedconnectivity.app.NetworkProviderInfo> CREATOR;
     field public static final int DEVICE_TYPE_AUTO = 5; // 0x5
@@ -9552,7 +9551,7 @@
   public static final class NetworkProviderInfo.Builder {
     ctor public NetworkProviderInfo.Builder(@NonNull String, @NonNull String);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo build();
-    method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryCharging(boolean);
+    method @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status") @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryCharging(boolean);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryPercentage(@IntRange(from=0, to=100) int);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=4) int);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceName(@NonNull String);
@@ -9641,6 +9640,7 @@
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler);
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean setControllerAlwaysOn(boolean);
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setReaderMode(boolean);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int setTagIntentAppPreferenceForUser(int, @NonNull String, boolean);
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
     field public static final int TAG_INTENT_APP_PREF_RESULT_PACKAGE_NOT_FOUND = -1; // 0xffffffff
@@ -10482,7 +10482,7 @@
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS, android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public boolean isUserNameSet();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isUserOfType(@NonNull String);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isUserUnlockingOrUnlocked(@NonNull android.os.UserHandle);
-    method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", "android.permission.MANAGE_USERS"}) public boolean isUserVisible();
+    method public boolean isUserVisible();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean removeUser(@NonNull android.os.UserHandle);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public int removeUserWhenPossible(@NonNull android.os.UserHandle, boolean);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public void setBootUser(@NonNull android.os.UserHandle);
@@ -14717,6 +14717,7 @@
     field public static final String TYPE_IMS_STRING = "ims";
     field public static final String TYPE_MCX_STRING = "mcx";
     field public static final String TYPE_MMS_STRING = "mms";
+    field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String TYPE_RCS_STRING = "rcs";
     field public static final String TYPE_SUPL_STRING = "supl";
     field public static final String TYPE_VSIM_STRING = "vsim";
     field public static final String TYPE_XCAP_STRING = "xcap";
@@ -16741,6 +16742,23 @@
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.AntennaPosition> CREATOR;
   }
 
+  @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class NtnSignalStrength implements android.os.Parcelable {
+    ctor @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public NtnSignalStrength(@Nullable android.telephony.satellite.NtnSignalStrength);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents();
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int getLevel();
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.NtnSignalStrength> CREATOR;
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_GOOD = 3; // 0x3
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_GREAT = 4; // 0x4
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_MODERATE = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_NONE = 0; // 0x0
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_POOR = 1; // 0x1
+  }
+
+  @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface NtnSignalStrengthCallback {
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onNtnSignalStrengthChanged(@NonNull android.telephony.satellite.NtnSignalStrength);
+  }
+
   @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class PointingInfo implements android.os.Parcelable {
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents();
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public float getSatelliteAzimuthDegrees();
@@ -16776,6 +16794,7 @@
     method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getSatelliteAttachRestrictionReasonsForCarrier(int);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingSatelliteDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatelliteService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForNtnSignalStrengthChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.NtnSignalStrengthCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteDatagram(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDatagramCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteStateCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteProvisionStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteProvisionStateCallback);
@@ -16786,6 +16805,7 @@
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteProvisioned(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void requestIsSatelliteSupported(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestNtnSignalStrength(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.NtnSignalStrength,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteAttachEnabledForCarrier(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteCapabilities(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.SatelliteCapabilities,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteEnabled(boolean, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
@@ -16794,6 +16814,7 @@
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void setDeviceAlignedWithSatellite(boolean);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void startSatelliteTransmissionUpdates(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>, @NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void stopSatelliteTransmissionUpdates(@NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForNtnSignalStrengthChanged(@NonNull android.telephony.satellite.NtnSignalStrengthCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteDatagram(@NonNull android.telephony.satellite.SatelliteDatagramCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteModemStateChanged(@NonNull android.telephony.satellite.SatelliteStateCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteProvisionStateChanged(@NonNull android.telephony.satellite.SatelliteProvisionStateCallback);
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 4fb7b6b..a501031 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -251,10 +251,6 @@
     New API must be flagged with @FlaggedApi: method android.media.audiopolicy.AudioMixingRule.writeToParcel(android.os.Parcel,int)
 UnflaggedApi: android.media.audiopolicy.AudioPolicy#updateMixingRules(java.util.List<android.util.Pair<android.media.audiopolicy.AudioMix,android.media.audiopolicy.AudioMixingRule>>):
     New API must be flagged with @FlaggedApi: method android.media.audiopolicy.AudioPolicy.updateMixingRules(java.util.List<android.util.Pair<android.media.audiopolicy.AudioMix,android.media.audiopolicy.AudioMixingRule>>)
-UnflaggedApi: android.net.wifi.sharedconnectivity.app.NetworkProviderInfo#isBatteryCharging():
-    New API must be flagged with @FlaggedApi: method android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.isBatteryCharging()
-UnflaggedApi: android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder#setBatteryCharging(boolean):
-    New API must be flagged with @FlaggedApi: method android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder.setBatteryCharging(boolean)
 UnflaggedApi: android.nfc.cardemulation.AidGroup#CONTENTS_FILE_DESCRIPTOR:
     New API must be flagged with @FlaggedApi: field android.nfc.cardemulation.AidGroup.CONTENTS_FILE_DESCRIPTOR
 UnflaggedApi: android.nfc.cardemulation.AidGroup#PARCELABLE_WRITE_RETURN_VALUE:
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index b905287..03a58be 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -541,7 +541,7 @@
     field public static final String PERMITTED_INPUT_METHODS_POLICY = "permittedInputMethods";
     field public static final String PERSONAL_APPS_SUSPENDED_POLICY = "personalAppsSuspended";
     field public static final String SCREEN_CAPTURE_DISABLED_POLICY = "screenCaptureDisabled";
-    field public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling";
+    field @FlaggedApi("android.app.admin.flags.policy_engine_migration_v2_enabled") public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling";
   }
 
   public class DevicePolicyManager {
@@ -851,13 +851,13 @@
     method @NonNull public android.companion.AssociationInfo.Builder setRevoked(boolean);
     method @NonNull public android.companion.AssociationInfo.Builder setSelfManaged(boolean);
     method @NonNull public android.companion.AssociationInfo.Builder setSystemDataSyncFlags(int);
-    method @NonNull public android.companion.AssociationInfo.Builder setTag(@Nullable String);
+    method @FlaggedApi("android.companion.association_tag") @NonNull public android.companion.AssociationInfo.Builder setTag(@Nullable String);
     method @NonNull public android.companion.AssociationInfo.Builder setTimeApproved(long);
   }
 
   public final class CompanionDeviceManager {
     method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public void enableSecureTransport(boolean);
-    field public static final int MESSAGE_REQUEST_PING = 1669362552; // 0x63807378
+    field @FlaggedApi("android.companion.companion_transport_apis") public static final int MESSAGE_REQUEST_PING = 1669362552; // 0x63807378
   }
 
   public abstract class CompanionDeviceService extends android.app.Service {
@@ -2117,7 +2117,7 @@
 
   public class SharedConnectivityManager {
     method @Nullable public static android.net.wifi.sharedconnectivity.app.SharedConnectivityManager create(@NonNull android.content.Context, @NonNull String, @NonNull String);
-    method @NonNull public android.content.BroadcastReceiver getBroadcastReceiver();
+    method @FlaggedApi("com.android.wifi.flags.shared_connectivity_broadcast_receiver_test_api") @NonNull public android.content.BroadcastReceiver getBroadcastReceiver();
     method @Nullable public android.content.ServiceConnection getServiceConnection();
     method public void setService(@Nullable android.os.IInterface);
   }
@@ -2148,7 +2148,7 @@
   }
 
   public final class BugreportParams {
-    field public static final int BUGREPORT_MODE_MAX_VALUE = 7; // 0x7
+    field @FlaggedApi("android.os.bugreport_mode_max_value") public static final int BUGREPORT_MODE_MAX_VALUE = 7; // 0x7
   }
 
   public class Build {
@@ -3797,14 +3797,14 @@
   public final class InputMethodManager {
     method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void addVirtualStylusIdForTestSession();
     method public int getDisplayId();
-    method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getEnabledInputMethodListAsUser(@NonNull android.os.UserHandle);
-    method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(@NonNull String, boolean, @NonNull android.os.UserHandle);
+    method @FlaggedApi("android.view.inputmethod.imm_userhandle_hostsidetests") @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getEnabledInputMethodListAsUser(@NonNull android.os.UserHandle);
+    method @FlaggedApi("android.view.inputmethod.imm_userhandle_hostsidetests") @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(@NonNull String, boolean, @NonNull android.os.UserHandle);
     method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getInputMethodListAsUser(int);
     method public boolean hasActiveInputConnection(@Nullable android.view.View);
     method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean hasPendingImeVisibilityRequests();
     method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean isCurrentRootView(@NonNull android.view.View);
     method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean isInputMethodPickerShown();
-    method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public boolean isStylusHandwritingAvailableAsUser(@NonNull android.os.UserHandle);
+    method @FlaggedApi("android.view.inputmethod.imm_userhandle_hostsidetests") @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public boolean isStylusHandwritingAvailableAsUser(@NonNull android.os.UserHandle);
     method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void setStylusWindowIdleTimeoutForTest(long);
     field public static final long CLEAR_SHOW_FORCED_FLAG_WHEN_LEAVING = 214016041L; // 0xcc1a029L
   }
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index 93e39d5..105e764 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -257,8 +257,6 @@
     New API must be flagged with @FlaggedApi: method android.media.soundtrigger.SoundTriggerManager.loadSoundModel(android.hardware.soundtrigger.SoundTrigger.SoundModel)
 UnflaggedApi: android.media.soundtrigger.SoundTriggerManager.Model#getSoundModel():
     New API must be flagged with @FlaggedApi: method android.media.soundtrigger.SoundTriggerManager.Model.getSoundModel()
-UnflaggedApi: android.net.wifi.sharedconnectivity.app.SharedConnectivityManager#getBroadcastReceiver():
-    New API must be flagged with @FlaggedApi: method android.net.wifi.sharedconnectivity.app.SharedConnectivityManager.getBroadcastReceiver()
 UnflaggedApi: android.os.BatteryManager#BATTERY_PLUGGED_ANY:
     New API must be flagged with @FlaggedApi: field android.os.BatteryManager.BATTERY_PLUGGED_ANY
 UnflaggedApi: android.os.BugreportParams#BUGREPORT_MODE_MAX_VALUE:
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 0293f66..ddb221f 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -14,6 +14,15 @@
     hdrs: ["android/hardware/HardwareBuffer.aidl"],
 }
 
+// TODO (b/303286040): Remove this once |ENABLE_NFC_MAINLINE_FLAG| is rolled out
+filegroup {
+    name: "framework-core-nfc-infcadapter-sources",
+    srcs: [
+        "android/nfc/INfcAdapter.aidl",
+    ],
+    visibility: ["//frameworks/base/services/core"],
+}
+
 filegroup {
     name: "framework-core-sources",
     srcs: [
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index a538247..08c18c8 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -3483,12 +3483,10 @@
 
         // only do this if the user already has more than one preferred locale
         if (r.getConfiguration().getLocales().size() > 1) {
-            LocaleConfig lc = LocaleConfig.fromContextIgnoringOverride(this);
-            mResourcesManager.setLocaleList(lc != null
-                    && lc.getSupportedLocales() != null
-                    && !lc.getSupportedLocales().isEmpty()
-                    ? lc.getSupportedLocales()
-                    : null);
+            LocaleConfig lc = getUserId() < 0
+                    ? LocaleConfig.fromContextIgnoringOverride(this)
+                    : new LocaleConfig(this);
+            mResourcesManager.setLocaleConfig(lc);
         }
     }
 
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index a3b82e9..d7d6546 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -161,12 +161,6 @@
      */
     boolean isWallpaperBackupEligible(int which, int userId);
 
-    /*
-     * Keyguard: register a callback for being notified that lock-state relevant
-     * wallpaper content has changed.
-     */
-    boolean setLockWallpaperCallback(IWallpaperManagerCallback cb);
-
     /**
      * Returns the colors used by the lock screen or system wallpaper.
      *
@@ -253,13 +247,6 @@
     boolean isStaticWallpaper(int which);
 
     /**
-     * Temporary method for project b/197814683.
-     * Return true if the lockscreen wallpaper always uses a WallpaperService, not a static image.
-     * @hide
-     */
-     boolean isLockscreenLiveWallpaperEnabled();
-
-    /**
      * Temporary method for project b/270726737.
      * Return true if the wallpaper supports different crops for different display dimensions.
      * @hide
diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java
index 0857c96..369a781 100644
--- a/core/java/android/app/LocaleConfig.java
+++ b/core/java/android/app/LocaleConfig.java
@@ -16,11 +16,12 @@
 
 package android.app;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.content.Context;
-import android.content.pm.ApplicationInfo;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
@@ -31,6 +32,7 @@
 import android.util.Slog;
 import android.util.Xml;
 
+import com.android.internal.R;
 import com.android.internal.util.XmlUtils;
 
 import org.xmlpull.v1.XmlPullParserException;
@@ -40,7 +42,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.LinkedHashSet;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Set;
@@ -67,6 +69,8 @@
     public static final String TAG_LOCALE_CONFIG = "locale-config";
     public static final String TAG_LOCALE = "locale";
     private LocaleList mLocales;
+
+    private Locale mDefaultLocale;
     private int mStatus = STATUS_NOT_SPECIFIED;
 
     /**
@@ -133,15 +137,14 @@
                 return;
             }
         }
-        int resId = 0;
         Resources res = context.getResources();
+        //Get the resource id
+        int resId = context.getApplicationInfo().getLocaleConfigRes();
+        if (resId == 0) {
+            mStatus = STATUS_NOT_SPECIFIED;
+            return;
+        }
         try {
-            //Get the resource id
-            resId = new ApplicationInfo(context.getApplicationInfo()).getLocaleConfigRes();
-            if (resId == 0) {
-                mStatus = STATUS_NOT_SPECIFIED;
-                return;
-            }
             //Get the parser to read XML data
             XmlResourceParser parser = res.getXml(resId);
             parseLocaleConfig(parser, res);
@@ -195,8 +198,17 @@
         XmlUtils.beginDocument(parser, TAG_LOCALE_CONFIG);
         int outerDepth = parser.getDepth();
         AttributeSet attrs = Xml.asAttributeSet(parser);
-        // LinkedHashSet to preserve insertion order
-        Set<String> localeNames = new LinkedHashSet<>();
+
+        String defaultLocale = null;
+        if (android.content.res.Flags.defaultLocale()) {
+            TypedArray att = res.obtainAttributes(
+                    attrs, com.android.internal.R.styleable.LocaleConfig);
+            defaultLocale = att.getString(
+                    R.styleable.LocaleConfig_defaultLocale);
+            att.recycle();
+        }
+
+        Set<String> localeNames = new HashSet<>();
         while (XmlUtils.nextElementWithin(parser, outerDepth)) {
             if (TAG_LOCALE.equals(parser.getName())) {
                 final TypedArray attributes = res.obtainAttributes(
@@ -211,6 +223,15 @@
         }
         mStatus = STATUS_SUCCESS;
         mLocales = LocaleList.forLanguageTags(String.join(",", localeNames));
+        if (defaultLocale != null) {
+            if (localeNames.contains(defaultLocale)) {
+                mDefaultLocale = Locale.forLanguageTag(defaultLocale);
+            } else {
+                Slog.w(TAG, "Default locale specified that is not contained in the list: "
+                        + defaultLocale);
+                mStatus = STATUS_PARSING_FAILED;
+            }
+        }
     }
 
     /**
@@ -226,6 +247,17 @@
     }
 
     /**
+     * Returns the default locale if specified, otherwise null
+     *
+     * @return The default Locale or null
+     */
+    @SuppressLint("UseIcu")
+    @FlaggedApi(android.content.res.Flags.FLAG_DEFAULT_LOCALE)
+    public @Nullable Locale getDefaultLocale() {
+        return mDefaultLocale;
+    }
+
+    /**
      * Get the status of reading the resource file where the LocaleConfig was stored.
      *
      * <p>Distinguish "the application didn't provide the resource file" from "the application
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 1ecb5d3..6009c29 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -120,9 +120,9 @@
     private final ReferenceQueue<Resources> mResourcesReferencesQueue = new ReferenceQueue<>();
 
     /**
-     * The list of locales the app declares it supports.
+     * The localeConfig of the app.
      */
-    private LocaleList mLocaleList = LocaleList.getEmptyLocaleList();
+    private LocaleConfig mLocaleConfig = new LocaleConfig(LocaleList.getEmptyLocaleList());
 
     private static class ApkKey {
         public final String path;
@@ -1612,18 +1612,19 @@
     }
 
     /**
-     * Returns the LocaleList current set
+     * Returns the LocaleConfig current set
      */
-    public LocaleList getLocaleList() {
-        return mLocaleList;
+    public LocaleConfig getLocaleConfig() {
+        return mLocaleConfig;
     }
 
     /**
-     * Sets the LocaleList of app's supported locales
+     * Sets the LocaleConfig of the app
      */
-    public void setLocaleList(LocaleList localeList) {
-        if ((localeList != null) && !localeList.isEmpty()) {
-            mLocaleList = localeList;
+    public void setLocaleConfig(LocaleConfig localeConfig) {
+        if ((localeConfig != null) && (localeConfig.getSupportedLocales() != null)
+                && !localeConfig.getSupportedLocales().isEmpty()) {
+            mLocaleConfig = localeConfig;
         }
     }
 
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index e7a5b72..d660078 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -313,7 +313,6 @@
     private final Context mContext;
     private final boolean mWcgEnabled;
     private final ColorManagementProxy mCmProxy;
-    private static Boolean sIsLockscreenLiveWallpaperEnabled = null;
     private static Boolean sIsMultiCropEnabled = null;
 
     /**
@@ -841,29 +840,14 @@
     }
 
     /**
+     * TODO (b/305908217) remove
      * Temporary method for project b/197814683.
      * @return true if the lockscreen wallpaper always uses a wallpaperService, not a static image
      * @hide
      */
     @TestApi
     public boolean isLockscreenLiveWallpaperEnabled() {
-        return isLockscreenLiveWallpaperEnabledHelper();
-    }
-
-    private static boolean isLockscreenLiveWallpaperEnabledHelper() {
-        if (sGlobals == null) {
-            sIsLockscreenLiveWallpaperEnabled = SystemProperties.getBoolean(
-                    "persist.wm.debug.lockscreen_live_wallpaper", true);
-        }
-        if (sIsLockscreenLiveWallpaperEnabled == null) {
-            try {
-                sIsLockscreenLiveWallpaperEnabled =
-                        sGlobals.mService.isLockscreenLiveWallpaperEnabled();
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-        }
-        return sIsLockscreenLiveWallpaperEnabled;
+        return true;
     }
 
     /**
@@ -2446,12 +2430,7 @@
      */
     @RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
     public void clearWallpaper() {
-        if (isLockscreenLiveWallpaperEnabled()) {
-            clearWallpaper(FLAG_LOCK | FLAG_SYSTEM, mContext.getUserId());
-            return;
-        }
-        clearWallpaper(FLAG_LOCK, mContext.getUserId());
-        clearWallpaper(FLAG_SYSTEM, mContext.getUserId());
+        clearWallpaper(FLAG_LOCK | FLAG_SYSTEM, mContext.getUserId());
     }
 
     /**
@@ -2787,11 +2766,7 @@
      */
     @RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
     public void clear() throws IOException {
-        if (isLockscreenLiveWallpaperEnabled()) {
-            clear(FLAG_SYSTEM | FLAG_LOCK);
-            return;
-        }
-        setStream(openDefaultWallpaper(mContext, FLAG_SYSTEM), null, false);
+        clear(FLAG_SYSTEM | FLAG_LOCK);
     }
 
     /**
@@ -2816,16 +2791,7 @@
      */
     @RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
     public void clear(@SetWallpaperFlags int which) throws IOException {
-        if (isLockscreenLiveWallpaperEnabled()) {
-            clearWallpaper(which, mContext.getUserId());
-            return;
-        }
-        if ((which & FLAG_SYSTEM) != 0) {
-            clear();
-        }
-        if ((which & FLAG_LOCK) != 0) {
-            clearWallpaper(FLAG_LOCK, mContext.getUserId());
-        }
+        clearWallpaper(which, mContext.getUserId());
     }
 
     /**
@@ -2840,16 +2806,12 @@
     public static InputStream openDefaultWallpaper(Context context, @SetWallpaperFlags int which) {
         final String whichProp;
         final int defaultResId;
-        if (which == FLAG_LOCK && !isLockscreenLiveWallpaperEnabledHelper()) {
-            /* Factory-default lock wallpapers are not yet supported
-            whichProp = PROP_LOCK_WALLPAPER;
-            defaultResId = com.android.internal.R.drawable.default_lock_wallpaper;
-            */
-            return null;
-        } else {
-            whichProp = PROP_WALLPAPER;
-            defaultResId = com.android.internal.R.drawable.default_wallpaper;
-        }
+        /* Factory-default lock wallpapers are not yet supported.
+        whichProp = which == FLAG_LOCK ? PROP_LOCK_WALLPAPER : PROP_WALLPAPER;
+        defaultResId = which == FLAG_LOCK ? R.drawable.default_lock_wallpaper :  ....
+        */
+        whichProp = PROP_WALLPAPER;
+        defaultResId = R.drawable.default_wallpaper;
         final String path = SystemProperties.get(whichProp);
         final InputStream wallpaperInputStream = getWallpaperInputStream(path);
         if (wallpaperInputStream != null) {
@@ -2988,25 +2950,6 @@
     }
 
     /**
-     * Register a callback for lock wallpaper observation. Only the OS may use this.
-     *
-     * @return true on success; false on error.
-     * @hide
-     */
-    public boolean setLockWallpaperCallback(IWallpaperManagerCallback callback) {
-        if (sGlobals.mService == null) {
-            Log.w(TAG, "WallpaperService not running");
-            throw new RuntimeException(new DeadSystemException());
-        }
-
-        try {
-            return sGlobals.mService.setLockWallpaperCallback(callback);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Is the current system wallpaper eligible for backup?
      *
      * Only the OS itself may use this method.
diff --git a/core/java/android/app/admin/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java
index ad0af72..84b1ca5 100644
--- a/core/java/android/app/admin/DevicePolicyIdentifiers.java
+++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java
@@ -16,10 +16,13 @@
 
 package android.app.admin;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.TestApi;
+import android.app.admin.flags.Flags;
 import android.os.UserManager;
 
+
 import java.util.Objects;
 
 /**
@@ -164,6 +167,7 @@
      *
      * @hide
      */
+    @FlaggedApi(Flags.FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED)
     @TestApi
     public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling";
 
diff --git a/core/java/android/app/admin/SystemUpdatePolicy.java b/core/java/android/app/admin/SystemUpdatePolicy.java
index c760298..113a6dd 100644
--- a/core/java/android/app/admin/SystemUpdatePolicy.java
+++ b/core/java/android/app/admin/SystemUpdatePolicy.java
@@ -125,7 +125,7 @@
      *
      * <p>The system limits each update to one 30-day postponement. The period begins when the
      * system first postpones the update and setting new {@code TYPE_POSTPONE} policies won’t extend
-     * the period. If, after 30 days the update isn’t installed (through policy changes), the system
+     * the period. If, after 30 days the update isn't installed (through policy changes), the system
      * prompts the user to install the update.
      *
      * <p><strong>Note</strong>: Device manufacturers or carriers might choose to exempt important
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java b/core/java/android/app/admin/flags/FlagUtils.java
similarity index 68%
rename from services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java
rename to core/java/android/app/admin/flags/FlagUtils.java
index 7e17ef11..7c3c3d5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java
+++ b/core/java/android/app/admin/flags/FlagUtils.java
@@ -14,15 +14,20 @@
  * limitations under the License.
  */
 
-package com.android.server.devicepolicy.flags;
+package android.app.admin.flags;
 
-import static com.android.server.devicepolicy.flags.Flags.devicePolicySizeTrackingEnabled;
-import static com.android.server.devicepolicy.flags.Flags.policyEngineMigrationV2Enabled;
+import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
+import static android.app.admin.flags.Flags.policyEngineMigrationV2Enabled;
+import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
 
 import android.os.Binder;
 
+/**
+ *
+ * @hide
+ */
 public final class FlagUtils {
-    private FlagUtils(){}
+    private FlagUtils() {}
 
     public static boolean isPolicyEngineMigrationV2Enabled() {
         return Binder.withCleanCallingIdentity(() -> {
@@ -35,4 +40,10 @@
             return devicePolicySizeTrackingEnabled();
         });
     }
+
+    public static boolean isOnboardingBugreportV2Enabled() {
+        return Binder.withCleanCallingIdentity(() -> {
+            return onboardingBugreportV2Enabled();
+        });
+    }
 }
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
new file mode 100644
index 0000000..f99615f
--- /dev/null
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -0,0 +1,29 @@
+package: "android.app.admin.flags"
+
+flag {
+  name: "policy_engine_migration_v2_enabled"
+  namespace: "enterprise"
+  description: "V2 of the policy engine migrations for Android V"
+  bug: "289520697"
+}
+
+flag {
+  name: "device_policy_size_tracking_enabled"
+  namespace: "enterprise"
+  description: "Add feature to track the total policy size and have a max threshold."
+  bug: "281543351"
+}
+
+flag {
+  name: "onboarding_bugreport_v2_enabled"
+  namespace: "enterprise"
+  description: "Add feature to track required changes for enabled V2 of auto-capturing of onboarding bug reports."
+  bug: "302517677"
+}
+
+flag {
+  name: "cross_user_suspension_enabled"
+  namespace: "enterprise"
+  description: "Allow holders of INTERACT_ACROSS_USERS_FULL to suspend apps in different users."
+  bug: "263464464"
+}
diff --git a/core/java/android/app/usage/flags.aconfig b/core/java/android/app/usage/flags.aconfig
index d1f9067..f401a76 100644
--- a/core/java/android/app/usage/flags.aconfig
+++ b/core/java/android/app/usage/flags.aconfig
@@ -2,7 +2,7 @@
 
 flag {
     name: "user_interaction_type_api"
-    namespace: "power_optimization"
+    namespace: "backstage_power"
     description: "Feature flag for user interaction event report/query API"
     bug: "296061232"
 }
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index 6393c45..161fa79 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -144,6 +144,7 @@
      * @return the tag of this association.
      * @see CompanionDeviceManager#setAssociationTag(int, String)
      */
+    @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
     @Nullable
     public String getTag() {
         return mTag;
@@ -205,9 +206,8 @@
     /**
      * @return whether the association is managed by the companion application it belongs to.
      * @see AssociationRequest.Builder#setSelfManaged(boolean)
-     * @hide
      */
-    @SystemApi
+    @SuppressLint("UnflaggedApi") // promoting from @SystemApi
     public boolean isSelfManaged() {
         return mSelfManaged;
     }
@@ -459,6 +459,7 @@
         }
 
         /** @hide */
+        @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
         @TestApi
         @NonNull
         public Builder setTag(@Nullable String tag) {
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index a84845a..70811bb 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -23,6 +23,7 @@
 import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -216,12 +217,14 @@
      *
      * @hide
      */
+    @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
     @TestApi public static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN
     /**
      * Message header assigned to the remote authentication handshakes.
      *
      * @hide
      */
+    @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
     @SystemApi(client = MODULE_LIBRARIES)
     public static final int MESSAGE_REQUEST_REMOTE_AUTHENTICATION = 0x63827765; // ?RMA
     /**
@@ -229,6 +232,7 @@
      *
      * @hide
      */
+    @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
     @SystemApi(client = MODULE_LIBRARIES)
     public static final int MESSAGE_REQUEST_CONTEXT_SYNC = 0x63678883; // ?CXS
     /**
@@ -236,6 +240,7 @@
      *
      * @hide
      */
+    @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
     @SystemApi(client = MODULE_LIBRARIES)
     public static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES
 
@@ -873,6 +878,7 @@
      *
      * @hide
      */
+    @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
     @SystemApi(client = MODULE_LIBRARIES)
     public interface OnTransportsChangedListener {
         /**
@@ -892,6 +898,7 @@
      *
      * @hide
      */
+    @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
     @SystemApi(client = MODULE_LIBRARIES)
     @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
     public void addOnTransportsChangedListener(
@@ -913,6 +920,7 @@
      *
      * @hide
      */
+    @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
     @SystemApi(client = MODULE_LIBRARIES)
     @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
     public void removeOnTransportsChangedListener(
@@ -934,6 +942,7 @@
      *
      * @hide
      */
+    @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
     @SystemApi(client = MODULE_LIBRARIES)
     @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
     public void sendMessage(int messageType, @NonNull byte[] data, @NonNull int[] associationIds) {
@@ -951,6 +960,7 @@
      *
      * @hide
      */
+    @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
     @SystemApi(client = MODULE_LIBRARIES)
     public interface OnMessageReceivedListener {
         /**
@@ -964,6 +974,7 @@
      *
      * @hide
      */
+    @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
     @SystemApi(client = MODULE_LIBRARIES)
     @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
     public void addOnMessageReceivedListener(
@@ -983,6 +994,7 @@
      *
      * @hide
      */
+    @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
     @SystemApi(client = MODULE_LIBRARIES)
     @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
     public void removeOnMessageReceivedListener(int messageType,
@@ -1423,6 +1435,7 @@
      *                          of the companion device recorded by CompanionDeviceManager
      * @param tag the tag of this association
      */
+    @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
     @UserHandleAware
     public void setAssociationTag(int associationId, @NonNull String tag) {
         Objects.requireNonNull(tag, "tag cannot be null");
@@ -1447,6 +1460,7 @@
      *                          of the companion device recorded by CompanionDeviceManager
      * @see CompanionDeviceManager#setAssociationTag(int, String)
      */
+    @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
     @UserHandleAware
     public void clearAssociationTag(int associationId) {
         try {
diff --git a/core/java/android/companion/flags.aconfig b/core/java/android/companion/flags.aconfig
index b9e5609..4f9c849 100644
--- a/core/java/android/companion/flags.aconfig
+++ b/core/java/android/companion/flags.aconfig
@@ -5,4 +5,18 @@
     namespace: "companion"
     description: "Controls if the new Builder is exposed to test apis."
     bug: "296251481"
+}
+
+flag {
+    name: "companion_transport_apis"
+    namespace: "companion"
+    description: "Grants access to the companion transport apis."
+    bug: "288297505"
+}
+
+flag {
+    name: "association_tag"
+    namespace: "companion"
+    description: "Enable Association tag APIs "
+    bug: "289241123"
 }
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensor.java b/core/java/android/companion/virtual/sensor/VirtualSensor.java
index eaa1792..14c7997 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensor.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensor.java
@@ -115,6 +115,11 @@
         parcel.writeStrongBinder(mToken);
     }
 
+    @Override
+    public String toString() {
+        return "VirtualSensor{ mType=" + mType + ", mName='" + mName + "' }";
+    }
+
     /**
      * Send a sensor event to the system.
      */
diff --git a/core/java/android/content/ContextParams.java b/core/java/android/content/ContextParams.java
index 988a9c0..b844d35 100644
--- a/core/java/android/content/ContextParams.java
+++ b/core/java/android/content/ContextParams.java
@@ -16,7 +16,10 @@
 
 package android.content;
 
+import static android.permission.flags.Flags.FLAG_SHOULD_REGISTER_ATTRIBUTION_SOURCE;
+
 import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -102,6 +105,7 @@
      * registered.
      */
     @NonNull
+    @FlaggedApi(FLAG_SHOULD_REGISTER_ATTRIBUTION_SOURCE)
     public boolean shouldRegisterAttributionSource() {
         return mShouldRegisterAttributionSource;
     }
@@ -179,6 +183,7 @@
          *                       created should be registered.
          */
         @NonNull
+        @FlaggedApi(FLAG_SHOULD_REGISTER_ATTRIBUTION_SOURCE)
         public Builder setShouldRegisterAttributionSource(boolean shouldRegister) {
             mShouldRegisterAttributionSource = shouldRegister;
             return this;
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index b765562..7b6bad3 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -64,6 +64,7 @@
 import android.os.ResultReceiver;
 import android.os.ShellCommand;
 import android.os.StrictMode;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
 import android.provider.ContactsContract.QuickContact;
@@ -2796,6 +2797,8 @@
      * started and is no longer considered stopped.
      * <ul>
      * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
+     * <li> {@link #EXTRA_TIME} containing the {@link SystemClock#elapsedRealtime()
+     *          elapsed realtime} of when the package was unstopped.
      * </ul>
      *
      * <p class="note">This is a protected intent that can only be sent by the system.
@@ -2869,9 +2872,15 @@
      *
      * <p class="note">This is a protected intent that can only be sent
      * by the system.
+     * <p>
+     * Starting in {@link Build.VERSION_CODES#VANILLA_ICE_CREAM Android V}, an extra timestamp
+     * {@link #EXTRA_TIME} is included with this broadcast to indicate the exact time the package
+     * was restarted, in {@link SystemClock#elapsedRealtime() elapsed realtime}.
+     * </p>
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_PACKAGE_RESTARTED = "android.intent.action.PACKAGE_RESTARTED";
+
     /**
      * Broadcast Action: The user has cleared the data of a package.  This should
      * be preceded by {@link #ACTION_PACKAGE_RESTARTED}, after which all of
@@ -4283,6 +4292,14 @@
             "com.android.intent.action.SHOW_BRIGHTNESS_DIALOG";
 
     /**
+     * Intent Extra: holds boolean that determines whether brightness dialog is full width when
+     * in landscape mode.
+     * @hide
+     */
+    public static final String EXTRA_BRIGHTNESS_DIALOG_IS_FULL_WIDTH =
+            "android.intent.extra.BRIGHTNESS_DIALOG_IS_FULL_WIDTH";
+
+    /**
      * Activity Action: Shows the contrast setting dialog.
      * @hide
      */
@@ -6578,8 +6595,8 @@
             = "android.intent.extra.SHUTDOWN_USERSPACE_ONLY";
 
     /**
-     * Optional extra specifying a time in milliseconds since the Epoch. The value must be
-     * non-negative.
+     * Optional extra specifying a time in milliseconds. The timebase depends on the Intent
+     * including this extra. The value must be non-negative.
      * <p>
      * Type: long
      * </p>
diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
index c4547b8..df2d7e7 100644
--- a/core/java/android/content/om/FabricatedOverlay.java
+++ b/core/java/android/content/om/FabricatedOverlay.java
@@ -16,6 +16,7 @@
 
 package android.content.om;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -546,6 +547,7 @@
      * @param configuration The string representation of the config this overlay is enabled for
      */
     @NonNull
+    @FlaggedApi(android.content.res.Flags.FLAG_ASSET_FILE_DESCRIPTOR_FRRO)
     public void setResourceValue(
             @NonNull String resourceName,
             @NonNull AssetFileDescriptor value,
diff --git a/core/java/android/content/pm/ArchivedActivityParcel.aidl b/core/java/android/content/pm/ArchivedActivityParcel.aidl
index 7ab7ed1..74953ff 100644
--- a/core/java/android/content/pm/ArchivedActivityParcel.aidl
+++ b/core/java/android/content/pm/ArchivedActivityParcel.aidl
@@ -16,9 +16,12 @@
 
 package android.content.pm;
 
+import android.content.ComponentName;
+
 /** @hide */
 parcelable ArchivedActivityParcel {
     String title;
+    ComponentName originalComponentName;
     // PNG compressed bitmaps.
     byte[] iconBitmap;
     byte[] monochromeIconBitmap;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 1b60f8e..b15c9e4 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1256,8 +1256,10 @@
     public static final long MATCH_ARCHIVED_PACKAGES = 1L << 32;
 
     /**
-     * @hide
+     * Querying flag: always match components of packages in quarantined state.
+     * @see #isPackageQuarantined
      */
+    @FlaggedApi(android.content.pm.Flags.FLAG_QUARANTINED_ENABLED)
     public static final long MATCH_QUARANTINED_COMPONENTS = 0x100000000L;
 
     /**
@@ -9902,12 +9904,16 @@
 
     /**
      * Query if an app is currently quarantined.
+     * A misbehaving app can be quarantined by e.g. a system of another privileged entity.
+     * Quarantined apps are similar to disabled, but still visible in e.g. Launcher.
+     * Only activities of such apps can still be queried, but not services etc.
+     * Quarantined apps can't be bound to, and won't receive broadcasts.
+     * They can't be resolved, unless {@link #MATCH_QUARANTINED_COMPONENTS} specified.
      *
      * @return {@code true} if the given package is quarantined, {@code false} otherwise
      * @throws NameNotFoundException if the package could not be found.
-     *
-     * @hide
      */
+    @FlaggedApi(android.content.pm.Flags.FLAG_QUARANTINED_ENABLED)
     public boolean isPackageQuarantined(@NonNull String packageName) throws NameNotFoundException {
         throw new UnsupportedOperationException("isPackageQuarantined not implemented");
     }
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 5cc3b92..c7790bd 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -27,6 +27,8 @@
 import android.annotation.RawRes;
 import android.annotation.StyleRes;
 import android.annotation.StyleableRes;
+import android.app.LocaleConfig;
+import android.app.ResourcesManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ActivityInfo.Config;
@@ -426,38 +428,59 @@
 
                 String[] selectedLocales = null;
                 String defaultLocale = null;
+                LocaleConfig lc = ResourcesManager.getInstance().getLocaleConfig();
                 if ((configChanges & ActivityInfo.CONFIG_LOCALE) != 0) {
                     if (locales.size() > 1) {
-                        String[] availableLocales;
-                        // The LocaleList has changed. We must query the AssetManager's
-                        // available Locales and figure out the best matching Locale in the new
-                        // LocaleList.
-                        availableLocales = mAssets.getNonSystemLocales();
-                        if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
-                            // No app defined locales, so grab the system locales.
-                            availableLocales = mAssets.getLocales();
-                            if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
-                                availableLocales = null;
+                        if (Flags.defaultLocale() && (lc.getDefaultLocale() != null)) {
+                            Locale[] intersection =
+                                    locales.getIntersection(lc.getSupportedLocales());
+                            mConfiguration.setLocales(new LocaleList(intersection));
+                            selectedLocales = new String[intersection.length];
+                            for (int i = 0; i < intersection.length; i++) {
+                                selectedLocales[i] =
+                                        adjustLanguageTag(intersection[i].toLanguageTag());
                             }
-                        }
+                            defaultLocale =
+                                    adjustLanguageTag(lc.getDefaultLocale().toLanguageTag());
+                        } else {
+                            String[] availableLocales;
+                            // The LocaleList has changed. We must query the AssetManager's
+                            // available Locales and figure out the best matching Locale in the new
+                            // LocaleList.
+                            availableLocales = mAssets.getNonSystemLocales();
+                            if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
+                                // No app defined locales, so grab the system locales.
+                                availableLocales = mAssets.getLocales();
+                                if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
+                                    availableLocales = null;
+                                }
+                            }
 
-                        if (availableLocales != null) {
-                            final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
-                                    availableLocales);
-                            if (bestLocale != null) {
-                                selectedLocales = new String[]{
-                                        adjustLanguageTag(bestLocale.toLanguageTag())};
-                                if (!bestLocale.equals(locales.get(0))) {
-                                    mConfiguration.setLocales(
-                                            new LocaleList(bestLocale, locales));
+                            if (availableLocales != null) {
+                                final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
+                                        availableLocales);
+                                if (bestLocale != null) {
+                                    selectedLocales = new String[]{
+                                            adjustLanguageTag(bestLocale.toLanguageTag())};
+                                    if (!bestLocale.equals(locales.get(0))) {
+                                        mConfiguration.setLocales(
+                                                new LocaleList(bestLocale, locales));
+                                    }
                                 }
                             }
                         }
                     }
                 }
                 if (selectedLocales == null) {
-                    selectedLocales = new String[]{
-                            adjustLanguageTag(locales.get(0).toLanguageTag())};
+                    if (Flags.defaultLocale() && (lc.getDefaultLocale() != null)) {
+                        selectedLocales = new String[locales.size()];
+                        for (int i = 0; i < locales.size(); i++) {
+                            selectedLocales[i] = adjustLanguageTag(locales.get(i).toLanguageTag());
+                        }
+                    } else {
+                        selectedLocales = new String[]{
+                                adjustLanguageTag(locales.get(0).toLanguageTag())};
+                    }
                 }
 
                 if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) {
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
new file mode 100644
index 0000000..1b8eb07
--- /dev/null
+++ b/core/java/android/content/res/flags.aconfig
@@ -0,0 +1,17 @@
+package: "android.content.res"
+
+flag {
+    name: "default_locale"
+    namespace: "resource_manager"
+    description: "Feature flag for default locale in LocaleConfig"
+    bug: "117306409"
+    # fixed_read_only or device wont boot because of permission issues accessing flags during boot
+    is_fixed_read_only: true
+}
+
+flag {
+    name: "asset_file_descriptor_frro"
+    namespace: "resource_manager"
+    description: "Feature flag for passing in an AssetFileDescriptor to create an frro"
+    bug: "304478666"
+}
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index b003e75..8a4f678 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -701,7 +701,7 @@
      *   }
      * </pre>
      */
-    @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
+    @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
     public void beginTransactionReadOnly() {
         beginTransactionWithListenerReadOnly(null);
     }
@@ -785,7 +785,7 @@
      *   }
      * </pre>
      */
-    @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
+    @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
     public void beginTransactionWithListenerReadOnly(
             @Nullable SQLiteTransactionListener transactionListener) {
         beginTransaction(transactionListener, SQLiteSession.TRANSACTION_MODE_DEFERRED);
@@ -2224,7 +2224,7 @@
      * @throws IllegalStateException if a transaction is not in progress.
      * @throws SQLiteException if the SQL cannot be compiled.
      */
-    @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
+    @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
     @NonNull
     public SQLiteRawStatement createRawStatement(@NonNull String sql) {
         Objects.requireNonNull(sql);
@@ -2244,7 +2244,7 @@
      * @return The ROWID of the last row to be inserted under this connection.
      * @throws IllegalStateException if there is no current transaction.
      */
-    @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
+    @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
     public long getLastInsertRowId() {
         return getThreadSession().getLastInsertRowId();
     }
@@ -2258,7 +2258,7 @@
      * @return The number of rows changed by the most recent sql statement
      * @throws IllegalStateException if there is no current transaction.
      */
-    @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
+    @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
     public long getLastChangedRowCount() {
         return getThreadSession().getLastChangedRowCount();
     }
@@ -2286,7 +2286,7 @@
      * @return The number of rows changed on the current connection.
      * @throws IllegalStateException if there is no current transaction.
      */
-    @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
+    @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
     public long getTotalChangedRowCount() {
         return getThreadSession().getTotalChangedRowCount();
     }
diff --git a/core/java/android/database/sqlite/SQLiteRawStatement.java b/core/java/android/database/sqlite/SQLiteRawStatement.java
index 827420f..33f602b 100644
--- a/core/java/android/database/sqlite/SQLiteRawStatement.java
+++ b/core/java/android/database/sqlite/SQLiteRawStatement.java
@@ -71,7 +71,7 @@
  *
  * @see <a href="http://sqlite.org/c3ref/stmt.html">sqlite3_stmt</a>
  */
-@FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
+@FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
 public final class SQLiteRawStatement implements Closeable {
 
     private static final String TAG = "SQLiteRawStatement";
diff --git a/core/java/android/database/sqlite/flags.aconfig b/core/java/android/database/sqlite/flags.aconfig
index 564df03..62a5123 100644
--- a/core/java/android/database/sqlite/flags.aconfig
+++ b/core/java/android/database/sqlite/flags.aconfig
@@ -1,7 +1,7 @@
 package: "android.database.sqlite"
 
 flag {
-     name: "sqlite_apis_15"
+     name: "sqlite_apis_35"
      namespace: "system_performance"
      is_fixed_read_only: true
      description: "SQLite APIs held back for Android 15"
diff --git a/core/java/android/hardware/HardwareBuffer.aidl b/core/java/android/hardware/HardwareBuffer.aidl
index 1333f0d..a9742cb 100644
--- a/core/java/android/hardware/HardwareBuffer.aidl
+++ b/core/java/android/hardware/HardwareBuffer.aidl
@@ -16,4 +16,4 @@
 
 package android.hardware;
 
-@JavaOnlyStableParcelable @NdkOnlyStableParcelable parcelable HardwareBuffer ndk_header "android/hardware_buffer_aidl.h";
+@JavaOnlyStableParcelable @NdkOnlyStableParcelable @RustOnlyStableParcelable parcelable HardwareBuffer ndk_header "android/hardware_buffer_aidl.h" rust_type "nativewindow::HardwareBuffer";
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index f033f97..bcf447b 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -16,6 +16,7 @@
 
 package android.hardware;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
@@ -27,6 +28,8 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -1809,6 +1812,41 @@
     protected abstract boolean cancelTriggerSensorImpl(TriggerEventListener listener,
             Sensor sensor, boolean disable);
 
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({DATA_INJECTION, REPLAY_DATA_INJECTION, HAL_BYPASS_REPLAY_DATA_INJECTION})
+    public @interface DataInjectionMode {}
+    /**
+     * This mode is only used for testing purposes. Not all HALs support this mode. In this mode,
+     * the HAL ignores the sensor data provided by physical sensors and accepts the data that is
+     * injected from the SensorService as if it were the real sensor data. This mode is primarily
+     * used for testing various algorithms like vendor provided SensorFusion, Step Counter and
+     * Step Detector etc. Typically, in this mode, there is a client app which injects
+     * sensor data into the HAL. Normal apps can register and unregister for any sensor
+     * that supports injection. Registering to sensors that do not support injection will
+     * give an error.
+     * This is the default data injection mode.
+     * @hide
+     */
+    public static final int DATA_INJECTION = 1;
+    /**
+     * Mostly equivalent to DATA_INJECTION with the difference being that the injected data is
+     * delivered to all requesting apps rather than just the package allowed to inject data.
+     * This mode is only allowed to be used on development builds.
+     * @hide
+     */
+    public static final int REPLAY_DATA_INJECTION = 3;
+    /**
+     * Like REPLAY_DATA_INJECTION but injected data is not sent into the HAL. It is stored in a
+     * buffer in the platform and played back to all requesting apps.
+     * This is useful for playing back sensor data to test platform components without
+     * relying on the HAL to support data injection.
+     * @hide
+     */
+    public static final int HAL_BYPASS_REPLAY_DATA_INJECTION = 4;
+
 
     /**
      * For testing purposes only. Not for third party applications.
@@ -1833,13 +1871,47 @@
      */
     @SystemApi
     public boolean initDataInjection(boolean enable) {
-        return initDataInjectionImpl(enable);
+        return initDataInjectionImpl(enable, DATA_INJECTION);
+    }
+
+    /**
+     * For testing purposes only. Not for third party applications.
+     *
+     * Initialize data injection mode and create a client for data injection. SensorService should
+     * already be operating in one of DATA_INJECTION, REPLAY_DATA_INJECTION or
+     * HAL_BYPASS_REPLAY_DATA_INJECTION modes for this call succeed. To set SensorService in
+     * a Data Injection mode, use one of:
+     *
+     * <ul>
+     *      <li>adb shell dumpsys sensorservice data_injection</li>
+     *      <li>adb shell dumpsys sensorservice replay_data_injection package_name</li>
+     *      <li>adb shell dumpsys sensorservice hal_bypass_replay_data_injection package_name</li>
+     * </ul>
+     *
+     * Typically this is done using a host side test.  This mode is expected to be used
+     * only for testing purposes. See {@link DataInjectionMode} for details of each data injection
+     * mode. Once this method succeeds, the test can call
+     * {@link #injectSensorData(Sensor, float[], int, long)} to inject sensor data into the HAL.
+     * To put SensorService back into normal mode, use "adb shell dumpsys sensorservice enable"
+     *
+     * @param enable True to initialize a client in a data injection mode.
+     *               False to clean up the native resources.
+     *
+     * @param mode One of DATA_INJECTION, REPLAY_DATA_INJECTION or HAL_BYPASS_DATA_INJECTION.
+     *             See {@link DataInjectionMode} for details.
+     *
+     * @return true if the HAL supports data injection and false
+     *         otherwise.
+     * @hide
+     */
+    public boolean initDataInjection(boolean enable, @DataInjectionMode int mode) {
+        return initDataInjectionImpl(enable, mode);
     }
 
     /**
      * @hide
      */
-    protected abstract boolean initDataInjectionImpl(boolean enable);
+    protected abstract boolean initDataInjectionImpl(boolean enable, @DataInjectionMode int mode);
 
     /**
      * For testing purposes only. Not for third party applications.
@@ -1871,9 +1943,6 @@
         if (sensor == null) {
             throw new IllegalArgumentException("sensor cannot be null");
         }
-        if (!sensor.isDataInjectionSupported()) {
-            throw new IllegalArgumentException("sensor does not support data injection");
-        }
         if (values == null) {
             throw new IllegalArgumentException("sensor data cannot be null");
         }
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index dfd3802..40e03db 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -90,6 +90,8 @@
     private static native void nativeGetRuntimeSensors(
             long nativeInstance, int deviceId, List<Sensor> list);
     private static native boolean nativeIsDataInjectionEnabled(long nativeInstance);
+    private static native boolean nativeIsReplayDataInjectionEnabled(long nativeInstance);
+    private static native boolean nativeIsHalBypassReplayDataInjectionEnabled(long nativeInstance);
 
     private static native int nativeCreateDirectChannel(
             long nativeInstance, int deviceId, long size, int channelType, int fd,
@@ -384,20 +386,41 @@
         }
     }
 
-    protected boolean initDataInjectionImpl(boolean enable) {
+    protected boolean initDataInjectionImpl(boolean enable, @DataInjectionMode int mode) {
         synchronized (sLock) {
+            boolean isDataInjectionModeEnabled = false;
             if (enable) {
-                boolean isDataInjectionModeEnabled = nativeIsDataInjectionEnabled(mNativeInstance);
+                switch (mode) {
+                    case DATA_INJECTION:
+                        isDataInjectionModeEnabled = nativeIsDataInjectionEnabled(mNativeInstance);
+                        break;
+                    case REPLAY_DATA_INJECTION:
+                        isDataInjectionModeEnabled = nativeIsReplayDataInjectionEnabled(
+                                mNativeInstance);
+                        break;
+                    case HAL_BYPASS_REPLAY_DATA_INJECTION:
+                        isDataInjectionModeEnabled = nativeIsHalBypassReplayDataInjectionEnabled(
+                                mNativeInstance);
+                        break;
+                    default:
+                        break;
+                }
                 // The HAL does not support injection OR SensorService hasn't been set in DI mode.
                 if (!isDataInjectionModeEnabled) {
-                    Log.e(TAG, "Data Injection mode not enabled");
+                    Log.e(TAG, "The correct Data Injection mode has not been enabled");
                     return false;
                 }
+                if (sInjectEventQueue != null && sInjectEventQueue.getDataInjectionMode() != mode) {
+                    // The inject event queue has been initialized for a different type of DI
+                    // close it and create a new one
+                    sInjectEventQueue.dispose();
+                    sInjectEventQueue = null;
+                }
                 // Initialize a client for data_injection.
                 if (sInjectEventQueue == null) {
                     try {
                         sInjectEventQueue = new InjectEventQueue(
-                                mMainLooper, this, mContext.getPackageName());
+                                mMainLooper, this, mode, mContext.getPackageName());
                     } catch (RuntimeException e) {
                         Log.e(TAG, "Cannot create InjectEventQueue: " + e);
                     }
@@ -421,6 +444,12 @@
                 Log.e(TAG, "Data injection mode not activated before calling injectSensorData");
                 return false;
             }
+            if (sInjectEventQueue.getDataInjectionMode() != HAL_BYPASS_REPLAY_DATA_INJECTION
+                    && !sensor.isDataInjectionSupported()) {
+                // DI mode and Replay DI mode require support from the sensor HAL
+                // HAL Bypass mode doesn't require this.
+                throw new IllegalArgumentException("sensor does not support data injection");
+            }
             int ret = sInjectEventQueue.injectSensorData(sensor.getHandle(), values, accuracy,
                                                          timestamp);
             // If there are any errors in data injection clean up the native resources.
@@ -825,6 +854,8 @@
 
         protected static final int OPERATING_MODE_NORMAL = 0;
         protected static final int OPERATING_MODE_DATA_INJECTION = 1;
+        protected static final int OPERATING_MODE_REPLAY_DATA_INJECTION = 3;
+        protected static final int OPERATING_MODE_HAL_BYPASS_REPLAY_DATA_INJECTION = 4;
 
         BaseEventQueue(Looper looper, SystemSensorManager manager, int mode, String packageName) {
             if (packageName == null) packageName = "";
@@ -1134,8 +1165,12 @@
     }
 
     final class InjectEventQueue extends BaseEventQueue {
-        public InjectEventQueue(Looper looper, SystemSensorManager manager, String packageName) {
-            super(looper, manager, OPERATING_MODE_DATA_INJECTION, packageName);
+
+        private int mMode;
+        public InjectEventQueue(Looper looper, SystemSensorManager manager,
+                @DataInjectionMode int mode, String packageName) {
+            super(looper, manager, mode, packageName);
+            mMode = mode;
         }
 
         int injectSensorData(int handle, float[] values, int accuracy, long timestamp) {
@@ -1161,6 +1196,10 @@
         protected void removeSensorEvent(Sensor sensor) {
 
         }
+
+        int getDataInjectionMode() {
+            return mMode;
+        }
     }
 
     protected boolean setOperationParameterImpl(SensorAdditionalInfo parameter) {
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 490ff64..7a43286 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -805,7 +805,7 @@
          * Get {@link Signature} object.
          * @return {@link Signature} object or null if this doesn't contain one.
          */
-        public Signature getSignature() {
+        public @Nullable Signature getSignature() {
             return super.getSignature();
         }
 
@@ -813,7 +813,7 @@
          * Get {@link Cipher} object.
          * @return {@link Cipher} object or null if this doesn't contain one.
          */
-        public Cipher getCipher() {
+        public @Nullable Cipher getCipher() {
             return super.getCipher();
         }
 
@@ -821,7 +821,7 @@
          * Get {@link Mac} object.
          * @return {@link Mac} object or null if this doesn't contain one.
          */
-        public Mac getMac() {
+        public @Nullable Mac getMac() {
             return super.getMac();
         }
 
diff --git a/core/java/android/hardware/biometrics/CryptoObject.java b/core/java/android/hardware/biometrics/CryptoObject.java
index 6ac1efb..39fbe83 100644
--- a/core/java/android/hardware/biometrics/CryptoObject.java
+++ b/core/java/android/hardware/biometrics/CryptoObject.java
@@ -20,6 +20,7 @@
 
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.security.identity.IdentityCredential;
 import android.security.identity.PresentationSession;
 import android.security.keystore2.AndroidKeyStoreProvider;
@@ -33,20 +34,35 @@
 /**
  * A wrapper class for the crypto objects supported by BiometricPrompt and FingerprintManager.
  * Currently the framework supports {@link Signature}, {@link Cipher}, {@link Mac},
- * {@link IdentityCredential}, and {@link PresentationSession} objects.
+ * {@link KeyAgreement}, {@link IdentityCredential}, and {@link PresentationSession} objects.
  * @hide
  */
 public class CryptoObject {
     private final Object mCrypto;
 
+    /**
+     * Create from a {@link Signature} object.
+     *
+     * @param signature a {@link Signature} object.
+     */
     public CryptoObject(@NonNull Signature signature) {
         mCrypto = signature;
     }
 
+    /**
+     * Create from a {@link Cipher} object.
+     *
+     * @param cipher a {@link Cipher} object.
+     */
     public CryptoObject(@NonNull Cipher cipher) {
         mCrypto = cipher;
     }
 
+    /**
+     * Create from a {@link Mac} object.
+     *
+     * @param mac a {@link Mac} object.
+     */
     public CryptoObject(@NonNull Mac mac) {
         mCrypto = mac;
     }
@@ -62,10 +78,20 @@
         mCrypto = credential;
     }
 
+    /**
+     * Create from a {@link PresentationSession} object.
+     *
+     * @param session a {@link PresentationSession} object.
+     */
     public CryptoObject(@NonNull PresentationSession session) {
         mCrypto = session;
     }
 
+    /**
+     * Create from a {@link KeyAgreement} object.
+     *
+     * @param keyAgreement a {@link KeyAgreement} object.
+     */
     @FlaggedApi(FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT)
     public CryptoObject(@NonNull KeyAgreement keyAgreement) {
         mCrypto = keyAgreement;
@@ -75,7 +101,7 @@
      * Get {@link Signature} object.
      * @return {@link Signature} object or null if this doesn't contain one.
      */
-    public Signature getSignature() {
+    public @Nullable Signature getSignature() {
         return mCrypto instanceof Signature ? (Signature) mCrypto : null;
     }
 
@@ -83,7 +109,7 @@
      * Get {@link Cipher} object.
      * @return {@link Cipher} object or null if this doesn't contain one.
      */
-    public Cipher getCipher() {
+    public @Nullable Cipher getCipher() {
         return mCrypto instanceof Cipher ? (Cipher) mCrypto : null;
     }
 
@@ -91,7 +117,7 @@
      * Get {@link Mac} object.
      * @return {@link Mac} object or null if this doesn't contain one.
      */
-    public Mac getMac() {
+    public @Nullable Mac getMac() {
         return mCrypto instanceof Mac ? (Mac) mCrypto : null;
     }
 
@@ -101,7 +127,7 @@
      * @deprecated Use {@link PresentationSession} instead of {@link IdentityCredential}.
      */
     @Deprecated
-    public IdentityCredential getIdentityCredential() {
+    public @Nullable IdentityCredential getIdentityCredential() {
         return mCrypto instanceof IdentityCredential ? (IdentityCredential) mCrypto : null;
     }
 
@@ -109,16 +135,18 @@
      * Get {@link PresentationSession} object.
      * @return {@link PresentationSession} object or null if this doesn't contain one.
      */
-    public PresentationSession getPresentationSession() {
+    public @Nullable PresentationSession getPresentationSession() {
         return mCrypto instanceof PresentationSession ? (PresentationSession) mCrypto : null;
     }
 
     /**
-     * Get {@link KeyAgreement} object.
+     * Get {@link KeyAgreement} object. A key-agreement protocol is a protocol whereby
+     * two or more parties can agree on a shared secret using public key cryptography.
+     *
      * @return {@link KeyAgreement} object or null if this doesn't contain one.
      */
     @FlaggedApi(FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT)
-    public KeyAgreement getKeyAgreement() {
+    public @Nullable KeyAgreement getKeyAgreement() {
         return mCrypto instanceof KeyAgreement ? (KeyAgreement) mCrypto : null;
     }
 
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 8decd50..2b5f5ee 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -220,7 +220,7 @@
 
         registerCallbackIfNeededLocked();
 
-        if (DEBUG || extraLogging()) {
+        if (DEBUG) {
             Log.d(TAG, "getDisplayInfo: displayId=" + displayId + ", info=" + info);
         }
         return info;
@@ -402,7 +402,7 @@
     }
 
     private void maybeLogAllDisplayListeners() {
-        if (!sExtraDisplayListenerLogging) {
+        if (!extraLogging()) {
             return;
         }
 
@@ -1222,7 +1222,7 @@
 
         private void handleMessage(Message msg) {
             if (extraLogging()) {
-                Slog.i(TAG, "DisplayListenerDelegate(" + eventToString(msg.what)
+                Slog.i(TAG, "DLD(" + eventToString(msg.what)
                         + ", display=" + msg.arg1
                         + ", mEventsMask=" + Long.toBinaryString(mEventsMask)
                         + ", mPackageName=" + mPackageName
@@ -1231,9 +1231,10 @@
             }
             if (DEBUG) {
                 Trace.beginSection(
-                        "DisplayListenerDelegate(" + eventToString(msg.what)
+                        TextUtils.trimToSize(
+                                "DLD(" + eventToString(msg.what)
                                 + ", display=" + msg.arg1
-                                + ", listener=" + mListener.getClass() + ")");
+                                + ", listener=" + mListener.getClass() + ")", 127));
             }
             switch (msg.what) {
                 case EVENT_DISPLAY_ADDED:
@@ -1422,11 +1423,12 @@
             sExtraDisplayListenerLogging = !TextUtils.isEmpty(EXTRA_LOGGING_PACKAGE_NAME)
                     && EXTRA_LOGGING_PACKAGE_NAME.equals(sCurrentPackageName);
         }
-        return sExtraDisplayListenerLogging;
+        // TODO: b/306170135 - return sExtraDisplayListenerLogging instead
+        return true;
     }
 
     private static boolean extraLogging() {
-        return sExtraDisplayListenerLogging && EXTRA_LOGGING_PACKAGE_NAME.equals(
-                sCurrentPackageName);
+        // TODO: b/306170135 - return sExtraDisplayListenerLogging & package name check instead
+        return true;
     }
 }
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 0221296..02304b5 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -1074,6 +1074,14 @@
      */
     public interface FaceDetectionCallback {
         void onFaceDetected(int sensorId, int userId, boolean isStrongBiometric);
+
+        /**
+         * An error has occurred with face detection.
+         *
+         * This callback signifies that this operation has been completed, and
+         * no more callbacks should be expected.
+         */
+        default void onDetectionError(int errorMsgId) {}
     }
 
     /**
@@ -1373,6 +1381,9 @@
         } else if (mRemovalCallback != null) {
             mRemovalCallback.onRemovalError(mRemovalFace, clientErrMsgId,
                     getErrorString(mContext, errMsgId, vendorCode));
+        } else if (mFaceDetectionCallback != null) {
+            mFaceDetectionCallback.onDetectionError(errMsgId);
+            mFaceDetectionCallback = null;
         }
     }
 
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 5bfda70..935157a 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -462,6 +462,14 @@
          * Invoked when a fingerprint has been detected.
          */
         void onFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric);
+
+        /**
+         * An error has occurred with fingerprint detection.
+         *
+         * This callback signifies that this operation has been completed, and
+         * no more callbacks should be expected.
+         */
+        default void onDetectionError(int errorMsgId) {}
     }
 
     /**
@@ -1484,6 +1492,9 @@
                     ? mRemoveTracker.mSingleFingerprint : null;
             mRemovalCallback.onRemovalError(fp, clientErrMsgId,
                     getErrorString(mContext, errMsgId, vendorCode));
+        } else if (mFingerprintDetectionCallback != null) {
+            mFingerprintDetectionCallback.onDetectionError(errMsgId);
+            mFingerprintDetectionCallback = null;
         }
     }
 
diff --git a/core/java/android/hardware/input/InputDeviceSensorManager.java b/core/java/android/hardware/input/InputDeviceSensorManager.java
index aa55e54..05024ea 100644
--- a/core/java/android/hardware/input/InputDeviceSensorManager.java
+++ b/core/java/android/hardware/input/InputDeviceSensorManager.java
@@ -644,7 +644,7 @@
         }
 
         @Override
-        protected boolean initDataInjectionImpl(boolean enable) {
+        protected boolean initDataInjectionImpl(boolean enable, int mode) {
             return false;
         }
 
diff --git a/core/java/android/nfc/Constants.java b/core/java/android/nfc/Constants.java
new file mode 100644
index 0000000..f768330
--- /dev/null
+++ b/core/java/android/nfc/Constants.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc;
+
+/**
+ * @hide
+ * TODO(b/303286040): Holds @hide API constants. Formalize these APIs.
+ */
+public final class Constants {
+    private Constants() { }
+
+    public static final String SETTINGS_SECURE_NFC_PAYMENT_FOREGROUND = "nfc_payment_foreground";
+    public static final String SETTINGS_SECURE_NFC_PAYMENT_DEFAULT_COMPONENT = "nfc_payment_default_component";
+    public static final String FEATURE_NFC_ANY = "android.hardware.nfc.any";
+}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 4658630..4a7bd3f 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -24,6 +24,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.UserIdInt;
 import android.app.Activity;
@@ -37,6 +38,7 @@
 import android.nfc.tech.Ndef;
 import android.nfc.tech.NfcA;
 import android.nfc.tech.NfcF;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
@@ -1594,6 +1596,40 @@
         mNfcActivityManager.disableReaderMode(activity);
     }
 
+    // Flags arguments to NFC adapter to enable/disable NFC
+    private static final int DISABLE_POLLING_FLAGS = 0x1000;
+    private static final int ENABLE_POLLING_FLAGS = 0x0000;
+
+    /**
+     * Privileged API to enable disable reader polling.
+     * Note: Use with caution! The app is responsible for ensuring that the polling state is
+     * returned to normal.
+     *
+     * @see #enableReaderMode(Activity, ReaderCallback, int, Bundle)  for more detailed
+     * documentation.
+     *
+     * @param enablePolling whether to enable or disable polling.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @SuppressLint("VisiblySynchronized")
+    public void setReaderMode(boolean enablePolling) {
+        synchronized (NfcAdapter.class) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
+        Binder token = new Binder();
+        int flags = enablePolling ? ENABLE_POLLING_FLAGS : DISABLE_POLLING_FLAGS;
+        try {
+            NfcAdapter.sService.setReaderMode(token, null, flags, null);
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+        }
+    }
+
     /**
      * Manually invoke Android Beam to share data.
      *
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
index 4909b08..32c2a1b 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -26,6 +26,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.nfc.Constants;
 import android.nfc.INfcCardEmulation;
 import android.nfc.NfcAdapter;
 import android.os.RemoteException;
@@ -274,7 +275,7 @@
             try {
                 preferForeground = Settings.Secure.getInt(
                         contextAsUser.getContentResolver(),
-                        Settings.Secure.NFC_PAYMENT_FOREGROUND) != 0;
+                        Constants.SETTINGS_SECURE_NFC_PAYMENT_FOREGROUND) != 0;
             } catch (SettingNotFoundException e) {
             }
             return preferForeground;
diff --git a/core/java/android/os/BugreportParams.java b/core/java/android/os/BugreportParams.java
index 47ad72f..e8ad303 100644
--- a/core/java/android/os/BugreportParams.java
+++ b/core/java/android/os/BugreportParams.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
@@ -133,6 +134,7 @@
      * The maximum value of supported bugreport mode.
      * @hide
      */
+    @FlaggedApi(android.os.Flags.FLAG_BUGREPORT_MODE_MAX_VALUE)
     @TestApi
     public static final int BUGREPORT_MODE_MAX_VALUE = BUGREPORT_MODE_ONBOARDING;
 
diff --git a/core/java/android/os/LocaleList.java b/core/java/android/os/LocaleList.java
index 82cdd28..d7e440b 100644
--- a/core/java/android/os/LocaleList.java
+++ b/core/java/android/os/LocaleList.java
@@ -153,21 +153,21 @@
 
     /**
      * Find the intersection between this LocaleList and another
-     * @return a String array of the Locales in both LocaleLists
+     * @return an array of the Locales in both LocaleLists
      * {@hide}
      */
     @NonNull
-    public String[] getIntersection(@NonNull LocaleList other) {
-        List<String> intersection = new ArrayList<>();
+    public Locale[] getIntersection(@NonNull LocaleList other) {
+        List<Locale> intersection = new ArrayList<>();
         for (Locale l1 : mList) {
             for (Locale l2 : other.mList) {
                 if (matchesLanguageAndScript(l2, l1)) {
-                    intersection.add(l1.toLanguageTag());
+                    intersection.add(l1);
                     break;
                 }
             }
         }
-        return intersection.toArray(new String[0]);
+        return intersection.toArray(new Locale[0]);
     }
 
     /**
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 4c8ef97..9034ff1 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -3330,7 +3330,10 @@
      *
      * @return whether the context user is running in the foreground.
      */
-    @UserHandleAware
+    @UserHandleAware(
+            requiresAnyOfPermissionsIfNotCaller = {
+                    android.Manifest.permission.MANAGE_USERS,
+                    android.Manifest.permission.INTERACT_ACROSS_USERS})
     public boolean isUserForeground() {
         try {
             return mService.isUserForeground(mUserId);
@@ -3404,11 +3407,10 @@
      * @hide
      */
     @SystemApi
-    @UserHandleAware
-    @RequiresPermission(anyOf = {
-            "android.permission.INTERACT_ACROSS_USERS",
-            "android.permission.MANAGE_USERS"
-    })
+    @UserHandleAware(
+            requiresAnyOfPermissionsIfNotCaller = {
+                    android.Manifest.permission.MANAGE_USERS,
+                    android.Manifest.permission.INTERACT_ACROSS_USERS})
     public boolean isUserVisible() {
         try {
             return mService.isUserVisible(mUserId);
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index 98f9dff..5078dc35 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -140,6 +140,31 @@
      */
     public static final int USAGE_MEDIA = 0x10 | USAGE_CLASS_MEDIA;
 
+    /** @hide */
+    @IntDef(prefix = { "CATEGORY_" }, value = {
+            CATEGORY_UNKNOWN,
+            CATEGORY_KEYBOARD,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Category {}
+
+    /**
+     * Category value when the vibration category is unknown.
+     *
+     * @hide
+     */
+    public static final int CATEGORY_UNKNOWN = 0x0;
+
+    /**
+     * Category value for keyboard vibrations.
+     *
+     * <p>Most typical keyboard vibrations are haptic feedback for virtual keyboard key
+     * press/release, for example.
+     *
+     * @hide
+     */
+    public static final int CATEGORY_KEYBOARD = 1;
+
     /**
      * @hide
      */
@@ -147,7 +172,8 @@
             FLAG_BYPASS_INTERRUPTION_POLICY,
             FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF,
             FLAG_INVALIDATE_SETTINGS_CACHE,
-            FLAG_PIPELINED_EFFECT
+            FLAG_PIPELINED_EFFECT,
+            FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Flag{}
@@ -167,6 +193,8 @@
      * {@link android.view.HapticFeedbackConstants#FLAG_IGNORE_GLOBAL_SETTING} and
      * {@link AudioAttributes#FLAG_BYPASS_MUTE}.
      *
+     * <p>Only privileged apps can ignore user settings, and this flag will be ignored otherwise.
+     *
      * @hide
      */
     public static final int FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF = 1 << 1;
@@ -199,12 +227,31 @@
     public static final int FLAG_PIPELINED_EFFECT = 1 << 3;
 
     /**
+     * Flag requesting that this vibration effect to be played without applying the user
+     * intensity setting to scale the vibration.
+     *
+     * <p>The user setting is still applied to enable/disable the vibration, but the vibration
+     * effect strength will not be scaled based on the enabled setting value.
+     *
+     * <p>This is intended to be used on scenarios where the system needs to enforce a specific
+     * strength for the vibration effect, regardless of the user preference. Only privileged apps
+     * can ignore user settings, and this flag will be ignored otherwise.
+     *
+     * <p>If you need to bypass the user setting when it's disabling vibrations then this also
+     * needs the flag {@link #FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF} to be set.
+     *
+     * @hide
+     */
+    public static final int FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE = 1 << 4;
+
+    /**
      * All flags supported by vibrator service, update it when adding new flag.
      * @hide
      */
     public static final int FLAG_ALL_SUPPORTED =
             FLAG_BYPASS_INTERRUPTION_POLICY | FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF
-                    | FLAG_INVALIDATE_SETTINGS_CACHE | FLAG_PIPELINED_EFFECT;
+                    | FLAG_INVALIDATE_SETTINGS_CACHE | FLAG_PIPELINED_EFFECT
+                    | FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE;
 
     /** Creates a new {@link VibrationAttributes} instance with given usage. */
     public static @NonNull VibrationAttributes createForUsage(@Usage int usage) {
@@ -214,12 +261,14 @@
     private final int mUsage;
     private final int mFlags;
     private final int mOriginalAudioUsage;
+    private final int mCategory;
 
     private VibrationAttributes(@Usage int usage, @AudioAttributes.AttributeUsage int audioUsage,
-            @Flag int flags) {
+            @Flag int flags, @Category int category) {
         mUsage = usage;
         mOriginalAudioUsage = audioUsage;
         mFlags = flags & FLAG_ALL_SUPPORTED;
+        mCategory = category;
     }
 
     /**
@@ -248,6 +297,20 @@
     }
 
     /**
+     * Return the vibration category.
+     *
+     * <p>Vibration categories describe the source of the vibration, and it can be combined with
+     * the vibration usage to best match to a user setting, e.g. a vibration with usage touch and
+     * category keyboard can be used to control keyboard haptic feedback independently.
+     *
+     * @hide
+     */
+    @Category
+    public int getCategory() {
+        return mCategory;
+    }
+
+    /**
      * Check whether a flag is set
      * @return true if a flag is set and false otherwise
      */
@@ -298,12 +361,14 @@
         dest.writeInt(mUsage);
         dest.writeInt(mOriginalAudioUsage);
         dest.writeInt(mFlags);
+        dest.writeInt(mCategory);
     }
 
     private VibrationAttributes(Parcel src) {
         mUsage = src.readInt();
         mOriginalAudioUsage = src.readInt();
         mFlags = src.readInt();
+        mCategory = src.readInt();
     }
 
     public static final @NonNull Parcelable.Creator<VibrationAttributes>
@@ -326,12 +391,12 @@
         }
         VibrationAttributes rhs = (VibrationAttributes) o;
         return mUsage == rhs.mUsage && mOriginalAudioUsage == rhs.mOriginalAudioUsage
-                && mFlags == rhs.mFlags;
+                && mFlags == rhs.mFlags && mCategory == rhs.mCategory;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mUsage, mOriginalAudioUsage, mFlags);
+        return Objects.hash(mUsage, mOriginalAudioUsage, mFlags, mCategory);
     }
 
     @Override
@@ -340,6 +405,7 @@
                 + "mUsage=" + usageToString()
                 + ", mAudioUsage= " + AudioAttributes.usageToString(mOriginalAudioUsage)
                 + ", mFlags=" + mFlags
+                + ", mCategory=" + categoryToString()
                 + '}';
     }
 
@@ -376,6 +442,23 @@
         }
     }
 
+    /** @hide */
+    public String categoryToString() {
+        return categoryToString(mCategory);
+    }
+
+    /** @hide */
+    public static String categoryToString(@Category int category) {
+        switch (category) {
+            case CATEGORY_UNKNOWN:
+                return "UNKNOWN";
+            case CATEGORY_KEYBOARD:
+                return "KEYBOARD";
+            default:
+                return "unknown category " + category;
+        }
+    }
+
     /**
      * Builder class for {@link VibrationAttributes} objects.
      * By default, all information is set to UNKNOWN.
@@ -384,6 +467,7 @@
         private int mUsage = USAGE_UNKNOWN;
         private int mOriginalAudioUsage = AudioAttributes.USAGE_UNKNOWN;
         private int mFlags = 0x0;
+        private int mCategory = CATEGORY_UNKNOWN;
 
         /**
          * Constructs a new Builder with the defaults.
@@ -399,6 +483,7 @@
                 mUsage = vib.mUsage;
                 mOriginalAudioUsage = vib.mOriginalAudioUsage;
                 mFlags = vib.mFlags;
+                mCategory = vib.mCategory;
             }
         }
 
@@ -464,7 +549,8 @@
          * @return a new {@link VibrationAttributes} object
          */
         public @NonNull VibrationAttributes build() {
-            VibrationAttributes ans = new VibrationAttributes(mUsage, mOriginalAudioUsage, mFlags);
+            VibrationAttributes ans = new VibrationAttributes(
+                    mUsage, mOriginalAudioUsage, mFlags, mCategory);
             return ans;
         }
 
@@ -480,6 +566,19 @@
         }
 
         /**
+         * Sets the attribute describing the category of the corresponding vibration.
+         *
+         * @param category The category for the vibration
+         * @return the same Builder instance.
+         *
+         * @hide
+         */
+        public @NonNull Builder setCategory(@Category int category) {
+            mCategory = category;
+            return this;
+        }
+
+        /**
          * Sets only the flags specified in the bitmask, leaving the other supported flag values
          * unchanged in the builder.
          *
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 99c9925..2fc2414 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -184,6 +184,16 @@
     }
 
     /**
+     * Whether the keyboard vibration is enabled by default.
+     *
+     * @return {@code true} if the keyboard vibration is default enabled, {@code false} otherwise.
+     * @hide
+     */
+    public boolean isDefaultKeyboardVibrationEnabled() {
+        return getConfig().isDefaultKeyboardVibrationEnabled();
+    }
+
+    /**
      * Return the ID of this vibrator.
      *
      * @return A non-negative integer representing the id of the vibrator controlled by this
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 7fceda4..c4521c0 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -16,7 +16,7 @@
 
 flag {
     name: "remove_app_profiler_pss_collection"
-    namespace: "power_optimization"
+    namespace: "backstage_power"
     description: "Replaces background PSS collection in AppProfiler with RSS"
     bug: "297542292"
 }
@@ -27,3 +27,10 @@
     description: "Guards a new Private Profile type in UserManager - everything from its setup to config to deletion."
     bug: "299069460"
 }
+
+flag {
+    name: "bugreport_mode_max_value"
+    namespace: "telephony"
+    description: "Introduce a constant as maximum value of bugreport mode."
+    bug: "305067125"
+}
diff --git a/core/java/android/os/vibrator/VibrationConfig.java b/core/java/android/os/vibrator/VibrationConfig.java
index bde334a..92e4967 100644
--- a/core/java/android/os/vibrator/VibrationConfig.java
+++ b/core/java/android/os/vibrator/VibrationConfig.java
@@ -65,6 +65,8 @@
     @VibrationIntensity
     private final int mDefaultRingVibrationIntensity;
 
+    private final boolean mDefaultKeyboardVibrationEnabled;
+
     /** @hide */
     public VibrationConfig(@Nullable Resources resources) {
         mHapticChannelMaxVibrationAmplitude = loadFloat(resources,
@@ -76,6 +78,8 @@
 
         mIgnoreVibrationsOnWirelessCharger = loadBoolean(resources,
                 com.android.internal.R.bool.config_ignoreVibrationsOnWirelessCharger, false);
+        mDefaultKeyboardVibrationEnabled = loadBoolean(resources,
+                com.android.internal.R.bool.config_defaultKeyboardVibrationEnabled, true);
 
         mDefaultAlarmVibrationIntensity = loadDefaultIntensity(resources,
                 com.android.internal.R.integer.config_defaultAlarmVibrationIntensity);
@@ -157,6 +161,14 @@
         return mIgnoreVibrationsOnWirelessCharger;
     }
 
+    /**
+     * Whether keyboard vibration settings is enabled by default.
+     * @hide
+     */
+    public boolean isDefaultKeyboardVibrationEnabled() {
+        return mDefaultKeyboardVibrationEnabled;
+    }
+
     /** Get the default vibration intensity for given usage. */
     @VibrationIntensity
     public int getDefaultVibrationIntensity(@VibrationAttributes.Usage int usage) {
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index 66ad12c..69d86a6 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -37,3 +37,10 @@
     is_fixed_read_only: true
     bug: "291128479"
 }
+
+flag {
+    namespace: "haptics"
+    name: "keyboard_category_enabled"
+    description: "Enables the independent keyboard vibration settings feature"
+    bug: "289107579"
+}
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index d60d4c6..3f06a91 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -28,3 +28,10 @@
   description: "enable AttributionSource.setNextAttributionSource"
   bug: "304478648"
 }
+
+flag {
+    name: "should_register_attribution_source"
+    namespace: "permissions"
+    description: "enable the shouldRegisterAttributionSource API"
+    bug: "305057691"
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index b34e09f..f0906b1 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5129,6 +5129,14 @@
                 "hardware_haptic_feedback_intensity";
 
         /**
+         * Whether keyboard vibration feedback is enabled. The value is boolean (1 or 0).
+         *
+         * @hide
+         */
+        @Readable
+        public static final String KEYBOARD_VIBRATION_ENABLED = "keyboard_vibration_enabled";
+
+        /**
          * Ringer volume. This is used internally, changing this value will not
          * change the volume. See AudioManager.
          *
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index a391571..27ad45d 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -3196,6 +3196,16 @@
         public static final String ALWAYS_ON = "always_on";
 
         /**
+         * The infrastructure bitmask which the APN can be used on. For example, some APNs can only
+         * be used when the device is on cellular, on satellite, or both. The default value is
+         * 1 (INFRASTRUCTURE_CELLULAR).
+         *
+         * <P>Type: INTEGER</P>
+         * @hide
+         */
+        public static final String INFRASTRUCTURE_BITMASK = "infrastructure_bitmask";
+
+        /**
          * MVNO type:
          * {@code SPN (Service Provider Name), IMSI, GID (Group Identifier Level 1)}.
          * <P>Type: TEXT</P>
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index c82a4ca..2a4cbaf 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -28,8 +28,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
-
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.List;
@@ -59,8 +57,7 @@
      * @hide
      */
     public NotificationRankingUpdate(Parcel in) {
-        if (SystemUiSystemPropertiesFlags.getResolver().isEnabled(
-                SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) {
+        if (Flags.rankingUpdateAshmem()) {
             // Recover the ranking map from the SharedMemory and store it in mapParcel.
             final Parcel mapParcel = Parcel.obtain();
             ByteBuffer buffer = null;
@@ -176,8 +173,7 @@
      */
     @Override
     public void writeToParcel(@NonNull Parcel out, int flags) {
-        if (SystemUiSystemPropertiesFlags.getResolver().isEnabled(
-                SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) {
+        if (Flags.rankingUpdateAshmem()) {
             final Parcel mapParcel = Parcel.obtain();
             ArrayList<NotificationListenerService.Ranking> marshalableRankings = new ArrayList<>();
             Bundle smartActionsBundle = new Bundle();
diff --git a/core/java/android/service/notification/flags.aconfig b/core/java/android/service/notification/flags.aconfig
new file mode 100644
index 0000000..2931435
--- /dev/null
+++ b/core/java/android/service/notification/flags.aconfig
@@ -0,0 +1,9 @@
+package: "android.service.notification"
+
+flag {
+  name: "ranking_update_ashmem"
+  namespace: "systemui"
+  description: "This flag controls moving ranking update contents into ashmem"
+  bug: "284297289"
+}
+
diff --git a/core/java/android/service/rotationresolver/OWNERS b/core/java/android/service/rotationresolver/OWNERS
index 5b57fc7..dce874d 100644
--- a/core/java/android/service/rotationresolver/OWNERS
+++ b/core/java/android/service/rotationresolver/OWNERS
@@ -1,9 +1,7 @@
 # Bug component: 814982
 
 asalo@google.com
-augale@google.com
 eejiang@google.com
 payamp@google.com
 siddikap@google.com
-svetoslavganov@google.com
 tgadh@google.com
diff --git a/core/java/android/service/voice/AbstractDetector.java b/core/java/android/service/voice/AbstractDetector.java
index 7af7fe6..db97d4f 100644
--- a/core/java/android/service/voice/AbstractDetector.java
+++ b/core/java/android/service/voice/AbstractDetector.java
@@ -199,8 +199,12 @@
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
+        Consumer<AbstractDetector> onDestroyListener;
         synchronized (mLock) {
-            mOnDestroyListener.accept(this);
+            onDestroyListener = mOnDestroyListener;
+        }
+        if (onDestroyListener != null) {
+            onDestroyListener.accept(this);
         }
     }
 
diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java
index 3ed13bb..35834fd 100644
--- a/core/java/android/speech/SpeechRecognizer.java
+++ b/core/java/android/speech/SpeechRecognizer.java
@@ -767,7 +767,7 @@
         try {
             mService.startListening(recognizerIntent, mListener, mContext.getAttributionSource());
             if (DBG) Log.d(TAG, "service start listening command succeeded");
-        } catch (final RemoteException e) {
+        } catch (final Exception e) {
             Log.e(TAG, "startListening() failed", e);
             mListener.onError(ERROR_CLIENT);
         }
@@ -781,7 +781,7 @@
         try {
             mService.stopListening(mListener);
             if (DBG) Log.d(TAG, "service stop listening command succeeded");
-        } catch (final RemoteException e) {
+        } catch (final Exception e) {
             Log.e(TAG, "stopListening() failed", e);
             mListener.onError(ERROR_CLIENT);
         }
@@ -795,7 +795,7 @@
         try {
             mService.cancel(mListener, /*isShutdown*/ false);
             if (DBG) Log.d(TAG, "service cancel command succeeded");
-        } catch (final RemoteException e) {
+        } catch (final Exception e) {
             Log.e(TAG, "cancel() failed", e);
             mListener.onError(ERROR_CLIENT);
         }
@@ -830,7 +830,7 @@
                     mContext.getAttributionSource(),
                     new InternalSupportCallback(callbackExecutor, recognitionSupportCallback));
             if (DBG) Log.d(TAG, "service support command succeeded");
-        } catch (final RemoteException e) {
+        } catch (final Exception e) {
             Log.e(TAG, "checkRecognitionSupport() failed", e);
             callbackExecutor.execute(() -> recognitionSupportCallback.onError(ERROR_CLIENT));
         }
@@ -850,7 +850,7 @@
                 mService.triggerModelDownload(
                         recognizerIntent, mContext.getAttributionSource(), null);
                 if (DBG) Log.d(TAG, "triggerModelDownload() without a listener");
-            } catch (final RemoteException e) {
+            } catch (final Exception e) {
                 Log.e(TAG, "triggerModelDownload() without a listener failed", e);
                 mListener.onError(ERROR_CLIENT);
             }
@@ -862,7 +862,7 @@
                         recognizerIntent, mContext.getAttributionSource(),
                         new InternalModelDownloadListener(callbackExecutor, modelDownloadListener));
                 if (DBG) Log.d(TAG, "triggerModelDownload() with a listener");
-            } catch (final RemoteException e) {
+            } catch (final Exception e) {
                 Log.e(TAG, "triggerModelDownload() with a listener failed", e);
                 callbackExecutor.execute(() -> modelDownloadListener.onError(ERROR_CLIENT));
             }
@@ -889,7 +889,7 @@
         if (mService != null) {
             try {
                 mService.cancel(mListener, /*isShutdown*/ true);
-            } catch (final RemoteException e) {
+            } catch (final Exception e) {
                 // Not important
             }
         }
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 2906d86..766e924 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -200,6 +200,12 @@
     public static final String SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION =
             "settings_remote_device_credential_validation";
 
+    /**
+     * Flag to enable/disable to start treating any calls to "suspend" an app as "quarantine".
+     * @hide
+     */
+    public static final String SETTINGS_TREAT_PAUSE_AS_QUARANTINE =
+            "settings_treat_pause_as_quarantine";
 
     private static final Map<String, String> DEFAULT_FLAGS;
 
@@ -247,6 +253,7 @@
         DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_FINGERPRINT_SETTINGS, "false");
         // TODO: b/298454866 Replace with Trunk Stable Feature Flag
         DEFAULT_FLAGS.put(SETTINGS_REMOTEAUTH_ENROLLMENT_SETTINGS, "false");
+        DEFAULT_FLAGS.put(SETTINGS_TREAT_PAUSE_AS_QUARANTINE, "false");
     }
 
     private static final Set<String> PERSISTENT_FLAGS;
@@ -264,6 +271,7 @@
         PERSISTENT_FLAGS.add(SETTINGS_ENABLE_SPA);
         PERSISTENT_FLAGS.add(SETTINGS_ENABLE_SPA_PHASE2);
         PERSISTENT_FLAGS.add(SETTINGS_PREFER_ACCESSIBILITY_MENU_IN_SYSTEM);
+        PERSISTENT_FLAGS.add(SETTINGS_TREAT_PAUSE_AS_QUARANTINE);
     }
 
     /**
diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index 1ed5d3f..71d382e 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -92,6 +92,12 @@
      * SurfaceView Surface, the buffer producer will already have access to the transform hint and
      * no additional work is needed.
      *
+     * If the root surface is not available, the API will return {@code BUFFER_TRANSFORM_IDENTITY}.
+     * The caller should register a listener to listen for any changes. @see
+     * {@link #addOnBufferTransformHintChangedListener(OnBufferTransformHintChangedListener)}.
+     * Warning: Calling this API in Android 14 (API Level 34) or earlier will crash if the root
+     * surface is not available.
+     *
      * @see HardwareBuffer
      */
     default @SurfaceControl.BufferTransform int getBufferTransformHint() {
diff --git a/core/java/android/view/ContentRecordingSession.java b/core/java/android/view/ContentRecordingSession.java
index a89f795..dc41b70 100644
--- a/core/java/android/view/ContentRecordingSession.java
+++ b/core/java/android/view/ContentRecordingSession.java
@@ -52,6 +52,12 @@
      */
     public static final int RECORD_CONTENT_TASK = 1;
 
+    /** Full screen sharing (app is not selected). */
+    public static final int TARGET_UID_FULL_SCREEN = -1;
+
+    /** Can't report (e.g. side loaded app). */
+    public static final int TARGET_UID_UNKNOWN = -2;
+
     /**
      * Unique logical identifier of the {@link android.hardware.display.VirtualDisplay} that has
      * recorded content rendered to its surface.
@@ -89,27 +95,36 @@
      */
     private boolean mWaitingForConsent = false;
 
+    /** UID of the package that is captured if selected. */
+    private int mTargetUid = TARGET_UID_UNKNOWN;
+
     /**
      * Default instance, with recording the display.
      */
     private ContentRecordingSession() {
     }
 
-    /**
-     * Returns an instance initialized for recording the indicated display.
-     */
+    /** Returns an instance initialized for recording the indicated display. */
     public static ContentRecordingSession createDisplaySession(int displayToMirror) {
-        return new ContentRecordingSession().setDisplayToRecord(displayToMirror)
-                .setContentToRecord(RECORD_CONTENT_DISPLAY);
+        return new ContentRecordingSession()
+                .setDisplayToRecord(displayToMirror)
+                .setContentToRecord(RECORD_CONTENT_DISPLAY)
+                .setTargetUid(TARGET_UID_FULL_SCREEN);
     }
 
-    /**
-     * Returns an instance initialized for task recording.
-     */
+    /** Returns an instance initialized for task recording. */
     public static ContentRecordingSession createTaskSession(
             @NonNull IBinder taskWindowContainerToken) {
-        return new ContentRecordingSession().setContentToRecord(RECORD_CONTENT_TASK)
-                .setTokenToRecord(taskWindowContainerToken);
+        return createTaskSession(taskWindowContainerToken, TARGET_UID_UNKNOWN);
+    }
+
+    /** Returns an instance initialized for task recording. */
+    public static ContentRecordingSession createTaskSession(
+            @NonNull IBinder taskWindowContainerToken, int targetUid) {
+        return new ContentRecordingSession()
+                .setContentToRecord(RECORD_CONTENT_TASK)
+                .setTokenToRecord(taskWindowContainerToken)
+                .setTargetUid(targetUid);
     }
 
     /**
@@ -175,13 +190,33 @@
         }
     }
 
+    @IntDef(prefix = "TARGET_UID_", value = {
+        TARGET_UID_FULL_SCREEN,
+        TARGET_UID_UNKNOWN
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface TargetUid {}
+
+    @DataClass.Generated.Member
+    public static String targetUidToString(@TargetUid int value) {
+        switch (value) {
+            case TARGET_UID_FULL_SCREEN:
+                    return "TARGET_UID_FULL_SCREEN";
+            case TARGET_UID_UNKNOWN:
+                    return "TARGET_UID_UNKNOWN";
+            default: return Integer.toHexString(value);
+        }
+    }
+
     @DataClass.Generated.Member
     /* package-private */ ContentRecordingSession(
             int virtualDisplayId,
             @RecordContent int contentToRecord,
             int displayToRecord,
             @Nullable IBinder tokenToRecord,
-            boolean waitingForConsent) {
+            boolean waitingForConsent,
+            int targetUid) {
         this.mVirtualDisplayId = virtualDisplayId;
         this.mContentToRecord = contentToRecord;
 
@@ -196,6 +231,7 @@
         this.mDisplayToRecord = displayToRecord;
         this.mTokenToRecord = tokenToRecord;
         this.mWaitingForConsent = waitingForConsent;
+        this.mTargetUid = targetUid;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -251,6 +287,14 @@
     }
 
     /**
+     * UID of the package that is captured if selected.
+     */
+    @DataClass.Generated.Member
+    public int getTargetUid() {
+        return mTargetUid;
+    }
+
+    /**
      * Unique logical identifier of the {@link android.hardware.display.VirtualDisplay} that has
      * recorded content rendered to its surface.
      */
@@ -314,6 +358,15 @@
         return this;
     }
 
+    /**
+     * UID of the package that is captured if selected.
+     */
+    @DataClass.Generated.Member
+    public @NonNull ContentRecordingSession setTargetUid( int value) {
+        mTargetUid = value;
+        return this;
+    }
+
     @Override
     @DataClass.Generated.Member
     public String toString() {
@@ -325,7 +378,8 @@
                 "contentToRecord = " + recordContentToString(mContentToRecord) + ", " +
                 "displayToRecord = " + mDisplayToRecord + ", " +
                 "tokenToRecord = " + mTokenToRecord + ", " +
-                "waitingForConsent = " + mWaitingForConsent +
+                "waitingForConsent = " + mWaitingForConsent + ", " +
+                "targetUid = " + mTargetUid +
         " }";
     }
 
@@ -346,7 +400,8 @@
                 && mContentToRecord == that.mContentToRecord
                 && mDisplayToRecord == that.mDisplayToRecord
                 && java.util.Objects.equals(mTokenToRecord, that.mTokenToRecord)
-                && mWaitingForConsent == that.mWaitingForConsent;
+                && mWaitingForConsent == that.mWaitingForConsent
+                && mTargetUid == that.mTargetUid;
     }
 
     @Override
@@ -361,6 +416,7 @@
         _hash = 31 * _hash + mDisplayToRecord;
         _hash = 31 * _hash + java.util.Objects.hashCode(mTokenToRecord);
         _hash = 31 * _hash + Boolean.hashCode(mWaitingForConsent);
+        _hash = 31 * _hash + mTargetUid;
         return _hash;
     }
 
@@ -378,6 +434,7 @@
         dest.writeInt(mContentToRecord);
         dest.writeInt(mDisplayToRecord);
         if (mTokenToRecord != null) dest.writeStrongBinder(mTokenToRecord);
+        dest.writeInt(mTargetUid);
     }
 
     @Override
@@ -397,6 +454,7 @@
         int contentToRecord = in.readInt();
         int displayToRecord = in.readInt();
         IBinder tokenToRecord = (flg & 0x8) == 0 ? null : (IBinder) in.readStrongBinder();
+        int targetUid = in.readInt();
 
         this.mVirtualDisplayId = virtualDisplayId;
         this.mContentToRecord = contentToRecord;
@@ -412,6 +470,7 @@
         this.mDisplayToRecord = displayToRecord;
         this.mTokenToRecord = tokenToRecord;
         this.mWaitingForConsent = waitingForConsent;
+        this.mTargetUid = targetUid;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -442,6 +501,7 @@
         private int mDisplayToRecord;
         private @Nullable IBinder mTokenToRecord;
         private boolean mWaitingForConsent;
+        private int mTargetUid;
 
         private long mBuilderFieldsSet = 0L;
 
@@ -513,10 +573,21 @@
             return this;
         }
 
+        /**
+         * UID of the package that is captured if selected.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setTargetUid(int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x20;
+            mTargetUid = value;
+            return this;
+        }
+
         /** Builds the instance. This builder should not be touched after calling this! */
         public @NonNull ContentRecordingSession build() {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x20; // Mark builder used
+            mBuilderFieldsSet |= 0x40; // Mark builder used
 
             if ((mBuilderFieldsSet & 0x1) == 0) {
                 mVirtualDisplayId = INVALID_DISPLAY;
@@ -533,17 +604,21 @@
             if ((mBuilderFieldsSet & 0x10) == 0) {
                 mWaitingForConsent = false;
             }
+            if ((mBuilderFieldsSet & 0x20) == 0) {
+                mTargetUid = TARGET_UID_UNKNOWN;
+            }
             ContentRecordingSession o = new ContentRecordingSession(
                     mVirtualDisplayId,
                     mContentToRecord,
                     mDisplayToRecord,
                     mTokenToRecord,
-                    mWaitingForConsent);
+                    mWaitingForConsent,
+                    mTargetUid);
             return o;
         }
 
         private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x20) != 0) {
+            if ((mBuilderFieldsSet & 0x40) != 0) {
                 throw new IllegalStateException(
                         "This Builder should not be reused. Use a new Builder instance instead");
             }
@@ -551,10 +626,10 @@
     }
 
     @DataClass.Generated(
-            time = 1683628463074L,
+            time = 1697456140720L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/view/ContentRecordingSession.java",
-            inputSignatures = "public static final  int RECORD_CONTENT_DISPLAY\npublic static final  int RECORD_CONTENT_TASK\nprivate  int mVirtualDisplayId\nprivate @android.view.ContentRecordingSession.RecordContent int mContentToRecord\nprivate  int mDisplayToRecord\nprivate @android.annotation.Nullable android.os.IBinder mTokenToRecord\nprivate  boolean mWaitingForConsent\npublic static  android.view.ContentRecordingSession createDisplaySession(int)\npublic static  android.view.ContentRecordingSession createTaskSession(android.os.IBinder)\npublic static  boolean isValid(android.view.ContentRecordingSession)\npublic static  boolean isProjectionOnSameDisplay(android.view.ContentRecordingSession,android.view.ContentRecordingSession)\nclass ContentRecordingSession extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genSetters=true, genEqualsHashCode=true)")
+            inputSignatures = "public static final  int RECORD_CONTENT_DISPLAY\npublic static final  int RECORD_CONTENT_TASK\npublic static final  int TARGET_UID_FULL_SCREEN\npublic static final  int TARGET_UID_UNKNOWN\nprivate  int mVirtualDisplayId\nprivate @android.view.ContentRecordingSession.RecordContent int mContentToRecord\nprivate  int mDisplayToRecord\nprivate @android.annotation.Nullable android.os.IBinder mTokenToRecord\nprivate  boolean mWaitingForConsent\nprivate  int mTargetUid\npublic static  android.view.ContentRecordingSession createDisplaySession(int)\npublic static  android.view.ContentRecordingSession createTaskSession(android.os.IBinder)\npublic static  android.view.ContentRecordingSession createTaskSession(android.os.IBinder,int)\npublic static  boolean isValid(android.view.ContentRecordingSession)\npublic static  boolean isProjectionOnSameDisplay(android.view.ContentRecordingSession,android.view.ContentRecordingSession)\nclass ContentRecordingSession extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genSetters=true, genEqualsHashCode=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 5069455..17c7fcc 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -2656,6 +2656,7 @@
             if (displayId == getDisplayId()) {
                 float newRatio = getHdrSdrRatio();
                 if (newRatio != mLastReportedRatio) {
+                    mLastReportedRatio = newRatio;
                     mListener.accept(Display.this);
                 }
             }
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 2f3d73a..42a0c9a 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -70,6 +70,7 @@
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemProperties;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Slog;
@@ -796,7 +797,7 @@
         if (nativeObject != 0) {
             // Only add valid surface controls to the registry. This is called at the end of this
             // method since its information is dumped if the process threshold is reached.
-            addToRegistry();
+            SurfaceControlRegistry.getProcessInstance().add(this);
         }
     }
 
@@ -1501,7 +1502,7 @@
             if (mCloseGuard != null) {
                 mCloseGuard.warnIfOpen();
             }
-            removeFromRegistry();
+            SurfaceControlRegistry.getProcessInstance().remove(this);
         } finally {
             super.finalize();
         }
@@ -1519,6 +1520,10 @@
      */
     public void release() {
         if (mNativeObject != 0) {
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "release", null, this, null);
+            }
             mFreeNativeResources.run();
             mNativeObject = 0;
             mNativeHandle = 0;
@@ -1532,7 +1537,7 @@
                     mChoreographer = null;
                 }
             }
-            removeFromRegistry();
+            SurfaceControlRegistry.getProcessInstance().remove(this);
         }
     }
 
@@ -2765,8 +2770,10 @@
 
         private Transaction(long nativeObject) {
             mNativeObject = nativeObject;
-            mFreeNativeResources =
-                    sRegistry.registerNativeAllocation(this, mNativeObject);
+            mFreeNativeResources = sRegistry.registerNativeAllocation(this, mNativeObject);
+            if (!SurfaceControlRegistry.sCallStackDebuggingInitialized) {
+                SurfaceControlRegistry.initializeCallStackDebugging();
+            }
         }
 
         private Transaction(Parcel in) {
@@ -2845,6 +2852,11 @@
             applyResizedSurfaces();
             notifyReparentedSurfaces();
             nativeApplyTransaction(mNativeObject, sync, oneWay);
+
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "apply", this, null, null);
+            }
         }
 
         /**
@@ -2920,6 +2932,10 @@
         @UnsupportedAppUsage
         public Transaction show(SurfaceControl sc) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "show", this, sc, null);
+            }
             nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SURFACE_HIDDEN);
             return this;
         }
@@ -2934,6 +2950,10 @@
         @UnsupportedAppUsage
         public Transaction hide(SurfaceControl sc) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "hide", this, sc, null);
+            }
             nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_HIDDEN, SURFACE_HIDDEN);
             return this;
         }
@@ -2950,6 +2970,10 @@
         @NonNull
         public Transaction setPosition(@NonNull SurfaceControl sc, float x, float y) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setPosition", this, sc, "x=" + x + " y=" + y);
+            }
             nativeSetPosition(mNativeObject, sc.mNativeObject, x, y);
             return this;
         }
@@ -2968,6 +2992,10 @@
             checkPreconditions(sc);
             Preconditions.checkArgument(scaleX >= 0, "Negative value passed in for scaleX");
             Preconditions.checkArgument(scaleY >= 0, "Negative value passed in for scaleY");
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setScale", this, sc, "sx=" + scaleX + " sy=" + scaleY);
+            }
             nativeSetScale(mNativeObject, sc.mNativeObject, scaleX, scaleY);
             return this;
         }
@@ -2985,6 +3013,10 @@
         public Transaction setBufferSize(@NonNull SurfaceControl sc,
                 @IntRange(from = 0) int w, @IntRange(from = 0) int h) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setBufferSize", this, sc, "w=" + w + " h=" + h);
+            }
             mResizedSurfaces.put(sc, new Point(w, h));
             return this;
         }
@@ -3005,6 +3037,10 @@
         public Transaction setFixedTransformHint(@NonNull SurfaceControl sc,
                        @Surface.Rotation int transformHint) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setFixedTransformHint", this, sc, "hint=" + transformHint);
+            }
             nativeSetFixedTransformHint(mNativeObject, sc.mNativeObject, transformHint);
             return this;
         }
@@ -3018,6 +3054,10 @@
         @NonNull
         public Transaction unsetFixedTransformHint(@NonNull SurfaceControl sc) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "unsetFixedTransformHint", this, sc, null);
+            }
             nativeSetFixedTransformHint(mNativeObject, sc.mNativeObject, -1/* INVALID_ROTATION */);
             return this;
         }
@@ -3035,6 +3075,10 @@
         public Transaction setLayer(@NonNull SurfaceControl sc,
                 @IntRange(from = Integer.MIN_VALUE, to = Integer.MAX_VALUE) int z) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setLayer", this, sc, "z=" + z);
+            }
             nativeSetLayer(mNativeObject, sc.mNativeObject, z);
             return this;
         }
@@ -3044,6 +3088,10 @@
          */
         public Transaction setRelativeLayer(SurfaceControl sc, SurfaceControl relativeTo, int z) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setRelativeLayer", this, sc, "relTo=" + relativeTo + " z=" + z);
+            }
             nativeSetRelativeLayer(mNativeObject, sc.mNativeObject, relativeTo.mNativeObject, z);
             return this;
         }
@@ -3053,6 +3101,10 @@
          */
         public Transaction setTransparentRegionHint(SurfaceControl sc, Region transparentRegion) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "unsetFixedTransformHint", this, sc, "region=" + transparentRegion);
+            }
             nativeSetTransparentRegionHint(mNativeObject,
                     sc.mNativeObject, transparentRegion);
             return this;
@@ -3069,6 +3121,10 @@
         public Transaction setAlpha(@NonNull SurfaceControl sc,
                 @FloatRange(from = 0.0, to = 1.0) float alpha) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setAlpha", this, sc, "alpha=" + alpha);
+            }
             nativeSetAlpha(mNativeObject, sc.mNativeObject, alpha);
             return this;
         }
@@ -3124,6 +3180,11 @@
         public Transaction setMatrix(SurfaceControl sc,
                 float dsdx, float dtdx, float dtdy, float dsdy) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setMatrix", this, sc,
+                        "dsdx=" + dsdx + " dtdx=" + dtdx + " dtdy=" + dtdy + " dsdy=" + dsdy);
+            }
             nativeSetMatrix(mNativeObject, sc.mNativeObject,
                     dsdx, dtdx, dtdy, dsdy);
             return this;
@@ -3189,6 +3250,10 @@
         @UnsupportedAppUsage
         public Transaction setWindowCrop(SurfaceControl sc, Rect crop) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setWindowCrop", this, sc, "crop=" + crop);
+            }
             if (crop != null) {
                 nativeSetWindowCrop(mNativeObject, sc.mNativeObject,
                         crop.left, crop.top, crop.right, crop.bottom);
@@ -3211,6 +3276,10 @@
          */
         public @NonNull Transaction setCrop(@NonNull SurfaceControl sc, @Nullable Rect crop) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setCrop", this, sc, "crop=" + crop);
+            }
             if (crop != null) {
                 Preconditions.checkArgument(crop.isValid(), "Crop isn't valid.");
                 nativeSetWindowCrop(mNativeObject, sc.mNativeObject,
@@ -3233,6 +3302,10 @@
          */
         public Transaction setWindowCrop(SurfaceControl sc, int width, int height) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setCornerRadius", this, sc, "w=" + width + " h=" + height);
+            }
             nativeSetWindowCrop(mNativeObject, sc.mNativeObject, 0, 0, width, height);
             return this;
         }
@@ -3247,6 +3320,10 @@
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         public Transaction setCornerRadius(SurfaceControl sc, float cornerRadius) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setCornerRadius", this, sc, "cornerRadius=" + cornerRadius);
+            }
             nativeSetCornerRadius(mNativeObject, sc.mNativeObject, cornerRadius);
 
             return this;
@@ -3262,6 +3339,10 @@
          */
         public Transaction setBackgroundBlurRadius(SurfaceControl sc, int radius) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setBackgroundBlurRadius", this, sc, "radius=" + radius);
+            }
             nativeSetBackgroundBlurRadius(mNativeObject, sc.mNativeObject, radius);
             return this;
         }
@@ -3318,6 +3399,10 @@
         public Transaction reparent(@NonNull SurfaceControl sc,
                 @Nullable SurfaceControl newParent) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "reparent", this, sc, "newParent=" + newParent);
+            }
             long otherObject = 0;
             if (newParent != null) {
                 newParent.checkNotReleased();
@@ -3337,6 +3422,11 @@
         @UnsupportedAppUsage
         public Transaction setColor(SurfaceControl sc, @Size(3) float[] color) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "reparent", this, sc,
+                        "r=" + color[0] + " g=" + color[1] + " b=" + color[2]);
+            }
             nativeSetColor(mNativeObject, sc.mNativeObject, color);
             return this;
         }
@@ -3347,6 +3437,10 @@
         */
         public Transaction unsetColor(SurfaceControl sc) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "unsetColor", this, sc, null);
+            }
             nativeSetColor(mNativeObject, sc.mNativeObject, INVALID_COLOR);
             return this;
         }
@@ -3358,6 +3452,10 @@
          */
         public Transaction setSecure(SurfaceControl sc, boolean isSecure) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setSecure", this, sc, "secure=" + isSecure);
+            }
             if (isSecure) {
                 nativeSetFlags(mNativeObject, sc.mNativeObject, SECURE, SECURE);
             } else {
@@ -3411,6 +3509,10 @@
         @NonNull
         public Transaction setOpaque(@NonNull SurfaceControl sc, boolean isOpaque) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setOpaque", this, sc, "opaque=" + isOpaque);
+            }
             if (isOpaque) {
                 nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_OPAQUE, SURFACE_OPAQUE);
             } else {
@@ -3580,6 +3682,10 @@
           */
         public Transaction setShadowRadius(SurfaceControl sc, float shadowRadius) {
             checkPreconditions(sc);
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "setShadowRadius", this, sc, "radius=" + shadowRadius);
+            }
             nativeSetShadowRadius(mNativeObject, sc.mNativeObject, shadowRadius);
             return this;
         }
@@ -4463,26 +4569,6 @@
         return -1;
     }
 
-    /**
-     * Adds this surface control to the registry for this process if it is created.
-     */
-    private void addToRegistry() {
-        final SurfaceControlRegistry registry = SurfaceControlRegistry.getProcessInstance();
-        if (registry != null) {
-            registry.add(this);
-        }
-    }
-
-    /**
-     * Removes this surface control from the registry for this process.
-     */
-    private void removeFromRegistry() {
-        final SurfaceControlRegistry registry = SurfaceControlRegistry.getProcessInstance();
-        if (registry != null) {
-            registry.remove(this);
-        }
-    }
-
     // Called by native
     private static void invokeReleaseCallback(Consumer<SyncFence> callback, long nativeFencePtr) {
         SyncFence fence = new SyncFence(nativeFencePtr);
diff --git a/core/java/android/view/SurfaceControlRegistry.java b/core/java/android/view/SurfaceControlRegistry.java
index 67ac811..52be8f6 100644
--- a/core/java/android/view/SurfaceControlRegistry.java
+++ b/core/java/android/view/SurfaceControlRegistry.java
@@ -23,7 +23,9 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.content.Context;
+import android.os.Build;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
@@ -78,6 +80,11 @@
             for (int i = 0; i < size; i++) {
                 final Map.Entry<SurfaceControl, Long> entry = entries.get(i);
                 final SurfaceControl sc = entry.getKey();
+                if (sc == null) {
+                    // Just skip if the key has since been removed from the weak hash map
+                    continue;
+                }
+
                 final long timeRegistered = entry.getValue();
                 pw.print("  ");
                 pw.print(sc.getName());
@@ -99,6 +106,9 @@
     // Number of surface controls to dump when the max threshold is exceeded
     private static final int DUMP_LIMIT = 256;
 
+    // An instance of a registry that is a no-op
+    private static final SurfaceControlRegistry NO_OP_REGISTRY = new NoOpRegistry();
+
     // Static lock, must be held for all registry operations
     private static final Object sLock = new Object();
 
@@ -108,6 +118,22 @@
     // The registry for a given process
     private static volatile SurfaceControlRegistry sProcessRegistry;
 
+    // Whether call stack debugging has been initialized. This is evaluated only once per process
+    // instance when the first SurfaceControl.Transaction object is created
+    static boolean sCallStackDebuggingInitialized;
+
+    // Whether call stack debugging is currently enabled, ie. whether there is a valid match string
+    // for either a specific surface control name or surface control transaction method
+    static boolean sCallStackDebuggingEnabled;
+
+    // The name of the surface control to log stack traces for.  Always non-null if
+    // sCallStackDebuggingEnabled is true.  Can be combined with the match call.
+    private static String sCallStackDebuggingMatchName;
+
+    // The surface control transaction method name to log stack traces for.  Always non-null if
+    // sCallStackDebuggingEnabled is true.  Can be combined with the match name.
+    private static String sCallStackDebuggingMatchCall;
+
     // Mapping of the active SurfaceControls to the elapsed time when they were registered
     @GuardedBy("sLock")
     private final WeakHashMap<SurfaceControl, Long> mSurfaceControls;
@@ -155,6 +181,12 @@
         }
     }
 
+    @VisibleForTesting
+    public void setCallStackDebuggingParams(String matchName, String matchCall) {
+        sCallStackDebuggingMatchName = matchName.toLowerCase();
+        sCallStackDebuggingMatchCall = matchCall.toLowerCase();
+    }
+
     /**
      * Creates and initializes the registry for all SurfaceControls in this process. The caller must
      * hold the READ_FRAME_BUFFER permission.
@@ -191,11 +223,9 @@
      * createProcessInstance(Context) was previously called from a valid caller.
      * @hide
      */
-    @Nullable
-    @VisibleForTesting
     public static SurfaceControlRegistry getProcessInstance() {
         synchronized (sLock) {
-            return sProcessRegistry;
+            return sProcessRegistry != null ? sProcessRegistry : NO_OP_REGISTRY;
         }
     }
 
@@ -243,6 +273,91 @@
     }
 
     /**
+     * Initializes global call stack debugging if this is a debug build and a filter is specified.
+     * This is a no-op if
+     *
+     * Usage:
+     *   adb shell setprop persist.wm.debug.sc.tx.log_match_call <call or \"\" to unset>
+     *   adb shell setprop persist.wm.debug.sc.tx.log_match_name <name or \"\" to unset>
+     *   adb reboot
+     */
+    final static void initializeCallStackDebugging() {
+        if (sCallStackDebuggingInitialized || !Build.IS_DEBUGGABLE) {
+            // Return early if already initialized or this is not a debug build
+            return;
+        }
+
+        sCallStackDebuggingInitialized = true;
+        sCallStackDebuggingMatchCall =
+                SystemProperties.get("persist.wm.debug.sc.tx.log_match_call", null)
+                        .toLowerCase();
+        sCallStackDebuggingMatchName =
+                SystemProperties.get("persist.wm.debug.sc.tx.log_match_name", null)
+                        .toLowerCase();
+        // Only enable stack debugging if any of the match filters are set
+        sCallStackDebuggingEnabled = (!sCallStackDebuggingMatchCall.isEmpty()
+                || !sCallStackDebuggingMatchName.isEmpty());
+        if (sCallStackDebuggingEnabled) {
+            Log.d(TAG, "Enabling transaction call stack debugging:"
+                    + " matchCall=" + sCallStackDebuggingMatchCall
+                    + " matchName=" + sCallStackDebuggingMatchName);
+        }
+    }
+
+    /**
+     * Dumps the callstack if it matches the global debug properties. Caller should first verify
+     * {@link #sCallStackDebuggingEnabled} is true.
+     *
+     * @param call the name of the call
+     * @param tx (optional) the transaction associated with this call
+     * @param sc the affected surface
+     * @param details additional details to print with the stack track
+     */
+    final void checkCallStackDebugging(@NonNull String call,
+            @Nullable SurfaceControl.Transaction tx, @Nullable SurfaceControl sc,
+            @Nullable String details) {
+        if (!sCallStackDebuggingEnabled) {
+            return;
+        }
+        if (!matchesForCallStackDebugging(sc != null ? sc.getName() : null, call)) {
+            return;
+        }
+        final String txMsg = tx != null ? "tx=" + tx.getId() + " ": "";
+        final String scMsg = sc != null ? " sc=" + sc.getName() + "": "";
+        final String msg = details != null
+                ? call + " (" + txMsg + scMsg + ") " + details
+                : call + " (" + txMsg + scMsg + ")";
+        Log.e(TAG, msg, new Throwable());
+    }
+
+    /**
+     * Tests whether the given surface control name/method call matches the filters set for the
+     * call stack debugging.
+     */
+    @VisibleForTesting
+    public final boolean matchesForCallStackDebugging(@Nullable String name, @NonNull String call) {
+        final boolean matchCall = !sCallStackDebuggingMatchCall.isEmpty();
+        if (matchCall && !call.toLowerCase().contains(sCallStackDebuggingMatchCall)) {
+            // Skip if target call doesn't match requested caller
+            return false;
+        }
+        final boolean matchName = !sCallStackDebuggingMatchName.isEmpty();
+        if (matchName && (name == null
+                || !name.toLowerCase().contains(sCallStackDebuggingMatchName))) {
+            // Skip if target surface doesn't match requested surface
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Returns whether call stack debugging is enabled for this process.
+     */
+    final static boolean isCallStackDebuggingEnabled() {
+        return sCallStackDebuggingEnabled;
+    }
+
+    /**
      * Forces the gc and finalizers to run, used prior to dumping to ensure we only dump strongly
      * referenced surface controls.
      */
@@ -267,7 +382,27 @@
         synchronized (sLock) {
             if (sProcessRegistry != null) {
                 sDefaultReporter.onMaxLayersExceeded(sProcessRegistry.mSurfaceControls, limit, pw);
+                pw.println("sCallStackDebuggingInitialized=" + sCallStackDebuggingInitialized);
+                pw.println("sCallStackDebuggingEnabled=" + sCallStackDebuggingEnabled);
+                pw.println("sCallStackDebuggingMatchName=" + sCallStackDebuggingMatchName);
+                pw.println("sCallStackDebuggingMatchCall=" + sCallStackDebuggingMatchCall);
             }
         }
     }
+
+    /**
+     * A no-op implementation of the registry.
+     */
+    private static class NoOpRegistry extends SurfaceControlRegistry {
+
+        @Override
+        public void setReportingThresholds(int maxLayersReportingThreshold,
+                int resetReportingThreshold, Reporter reporter) {}
+
+        @Override
+        void add(SurfaceControl sc) {}
+
+        @Override
+        void remove(SurfaceControl sc) {}
+    }
 }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9392783..4a10cab 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -655,6 +655,10 @@
      */
     private boolean mCheckIfCanDraw = false;
 
+    private boolean mWasLastDrawCanceled;
+    private boolean mLastTraversalWasVisible = true;
+    private boolean mLastDrawScreenOff;
+
     private boolean mDrewOnceForSync = false;
 
     int mSyncSeqId = 0;
@@ -1016,7 +1020,8 @@
         mDisplay = display;
         mBasePackageName = context.getBasePackageName();
         final String name = DisplayProperties.debug_vri_package().orElse(null);
-        mExtraDisplayListenerLogging = !TextUtils.isEmpty(name) && name.equals(mBasePackageName);
+        // TODO: b/306170135 - return to using textutils check on package name.
+        mExtraDisplayListenerLogging = true;
         mThread = Thread.currentThread();
         mLocation = new WindowLeaked(null);
         mLocation.fillInStackTrace();
@@ -1890,12 +1895,19 @@
     }
 
     void handleAppVisibility(boolean visible) {
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+            Trace.instant(Trace.TRACE_TAG_VIEW, TextUtils.formatSimple(
+                    "%s visibilityChanged oldVisibility=%b newVisibility=%b", mTag,
+                    mAppVisible, visible));
+        }
         if (mAppVisible != visible) {
             final boolean previousVisible = getHostVisibility() == View.VISIBLE;
             mAppVisible = visible;
             final boolean currentVisible = getHostVisibility() == View.VISIBLE;
             // Root view only cares about whether it is visible or not.
             if (previousVisible != currentVisible) {
+                Log.d(mTag, "visibilityChanged oldVisibility=" + previousVisible + " newVisibility="
+                        + currentVisible);
                 mAppVisibilityChanged = true;
                 scheduleTraversals();
             }
@@ -2003,6 +2015,10 @@
                     Slog.i(mTag, "DisplayState - old: " + oldDisplayState
                             + ", new: " + newDisplayState);
                 }
+                if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+                    Trace.traceCounter(Trace.TRACE_TAG_WINDOW_MANAGER,
+                            "vri#screenState[" + mTag + "] state=", newDisplayState);
+                }
                 if (oldDisplayState != newDisplayState) {
                     mAttachInfo.mDisplayState = newDisplayState;
                     pokeDrawLockIfNeeded();
@@ -3252,8 +3268,8 @@
                 || mForceNextWindowRelayout) {
             if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
                 Trace.traceBegin(Trace.TRACE_TAG_VIEW,
-                        TextUtils.formatSimple("relayoutWindow#"
-                                        + "first=%b/resize=%b/vis=%b/params=%b/force=%b",
+                        TextUtils.formatSimple("%s-relayoutWindow#"
+                                        + "first=%b/resize=%b/vis=%b/params=%b/force=%b", mTag,
                                 mFirst, windowShouldResize, viewVisibilityChanged, params != null,
                                 mForceNextWindowRelayout));
             }
@@ -3842,11 +3858,7 @@
         boolean cancelDueToPreDrawListener = mAttachInfo.mTreeObserver.dispatchOnPreDraw();
         boolean cancelAndRedraw = cancelDueToPreDrawListener
                  || (cancelDraw && mDrewOnceForSync);
-        if (cancelAndRedraw) {
-            Log.d(mTag, "Cancelling draw."
-                    + " cancelDueToPreDrawListener=" + cancelDueToPreDrawListener
-                    + " cancelDueToSync=" + (cancelDraw && mDrewOnceForSync));
-        }
+
         if (!cancelAndRedraw) {
             // A sync was already requested before the WMS requested sync. This means we need to
             // sync the buffer, regardless if WMS wants to sync the buffer.
@@ -3870,6 +3882,9 @@
         }
 
         if (!isViewVisible) {
+            if (mLastTraversalWasVisible) {
+                logAndTrace("Not drawing due to not visible");
+            }
             mLastPerformTraversalsSkipDrawReason = "view_not_visible";
             if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                 for (int i = 0; i < mPendingTransitions.size(); ++i) {
@@ -3881,12 +3896,23 @@
             handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mHasPendingTransactions,
                     mPendingTransaction, "view not visible");
         } else if (cancelAndRedraw) {
+            if (!mWasLastDrawCanceled) {
+                logAndTrace("Canceling draw."
+                        + " cancelDueToPreDrawListener=" + cancelDueToPreDrawListener
+                        + " cancelDueToSync=" + (cancelDraw && mDrewOnceForSync));
+            }
             mLastPerformTraversalsSkipDrawReason = cancelDueToPreDrawListener
                 ? "predraw_" + mAttachInfo.mTreeObserver.getLastDispatchOnPreDrawCanceledReason()
                 : "cancel_" + cancelReason;
             // Try again
             scheduleTraversals();
         } else {
+            if (mWasLastDrawCanceled) {
+                logAndTrace("Draw frame after cancel");
+            }
+            if (!mLastTraversalWasVisible) {
+                logAndTrace("Start draw after previous draw not visible");
+            }
             if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                 for (int i = 0; i < mPendingTransitions.size(); ++i) {
                     mPendingTransitions.get(i).startChangingAnimations();
@@ -3898,6 +3924,8 @@
                         mPendingTransaction, mLastPerformDrawSkippedReason);
             }
         }
+        mWasLastDrawCanceled = cancelAndRedraw;
+        mLastTraversalWasVisible = isViewVisible;
 
         if (mAttachInfo.mContentCaptureEvents != null) {
             notifyContentCaptureEvents();
@@ -4687,10 +4715,7 @@
 
                 return didProduceBuffer -> {
                     if (!didProduceBuffer) {
-                        Trace.instant(Trace.TRACE_TAG_VIEW,
-                                "Transaction not synced due to no frame drawn-" + mTag);
-                        Log.d(mTag, "Pending transaction will not be applied in sync with a draw "
-                                + "because there was nothing new to draw");
+                        logAndTrace("Transaction not synced due to no frame drawn");
                         mBlastBufferQueue.applyPendingTransactions(frame);
                     }
                 };
@@ -4707,17 +4732,26 @@
         mLastPerformDrawSkippedReason = null;
         if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
             mLastPerformDrawSkippedReason = "screen_off";
+            if (!mLastDrawScreenOff) {
+                logAndTrace("Not drawing due to screen off");
+            }
+            mLastDrawScreenOff = true;
             return false;
         } else if (mView == null) {
             mLastPerformDrawSkippedReason = "no_root_view";
             return false;
         }
 
+        if (mLastDrawScreenOff) {
+            logAndTrace("Resumed drawing after screen turned on");
+            mLastDrawScreenOff = false;
+        }
+
         final boolean fullRedrawNeeded = mFullRedrawNeeded || surfaceSyncGroup != null;
         mFullRedrawNeeded = false;
 
         mIsDrawing = true;
-        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
+        Trace.traceBegin(Trace.TRACE_TAG_VIEW, mTag + "-draw");
 
         addFrameCommitCallbackIfNeeded();
 
@@ -11426,8 +11460,7 @@
     @Override
     public boolean applyTransactionOnDraw(@NonNull SurfaceControl.Transaction t) {
         if (mRemoved || !isHardwareEnabled()) {
-            Trace.instant(Trace.TRACE_TAG_VIEW, "applyTransactionOnDraw applyImmediately-" + mTag);
-            Log.d(mTag, "applyTransactionOnDraw: Applying transaction immediately");
+            logAndTrace("applyTransactionOnDraw applyImmediately");
             t.apply();
         } else {
             Trace.instant(Trace.TRACE_TAG_VIEW, "applyTransactionOnDraw-" + mTag);
@@ -11441,7 +11474,11 @@
 
     @Override
     public @SurfaceControl.BufferTransform int getBufferTransformHint() {
-        return mSurfaceControl.getTransformHint();
+        if (mSurfaceControl.isValid()) {
+            return mSurfaceControl.getTransformHint();
+        } else {
+            return SurfaceControl.BUFFER_TRANSFORM_IDENTITY;
+        }
     }
 
     @Override
@@ -11810,4 +11847,11 @@
             @NonNull Consumer<Boolean> listener) {
         t.clearTrustedPresentationCallback(getSurfaceControl());
     }
+
+    private void logAndTrace(String msg) {
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+            Trace.instant(Trace.TRACE_TAG_VIEW, mTag + "-" + msg);
+        }
+        Log.d(mTag, msg);
+    }
 }
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index cc612ed..6888b50 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -1,10 +1,12 @@
 package: "android.view.accessibility"
 
+# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
+
 flag {
+    name: "a11y_overlay_callbacks"
     namespace: "accessibility"
-    name: "force_invert_color"
-    description: "Enable force force-dark for smart inversion and dark theme everywhere"
-    bug: "282821643"
+    description: "Whether to allow the passing of result callbacks when attaching a11y overlays."
+    bug: "304478691"
 }
 
 flag {
@@ -15,8 +17,8 @@
 }
 
 flag {
-        name: "a11y_overlay_callbacks"
     namespace: "accessibility"
-    description: "Whether to allow the passing of result callbacks when attaching a11y overlays."
-    bug: "304478691"
+    name: "force_invert_color"
+    description: "Enable force force-dark for smart inversion and dark theme everywhere"
+    bug: "282821643"
 }
diff --git a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
index 5c86feb..f6ee061 100644
--- a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
+++ b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
@@ -13,3 +13,10 @@
     description: "If true, content protection groups config will be parsed."
     bug: "302187922"
 }
+
+flag {
+    name: "setting_ui_enabled"
+    namespace: "content_protection"
+    description: "If true, content protection setting ui is displayed in Settings > Privacy & Security > More security & privacy."
+    bug: "305792348"
+}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 8159af3..eeab005 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -39,6 +39,7 @@
 import android.annotation.DisplayContext;
 import android.annotation.DrawableRes;
 import android.annotation.DurationMillisLong;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -1512,6 +1513,7 @@
      * Returns {@code true} if currently selected IME supports Stylus handwriting & is enabled.
      * If the method returns {@code false}, {@link #startStylusHandwriting(View)} shouldn't be
      * called and Stylus touch should continue as normal touch input.
+     *
      * @see #startStylusHandwriting(View)
      */
     public boolean isStylusHandwritingAvailable() {
@@ -1535,6 +1537,7 @@
     @NonNull
     @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
     @TestApi
+    @FlaggedApi(Flags.FLAG_IMM_USERHANDLE_HOSTSIDETESTS)
     @SuppressLint("UserHandle")
     public boolean isStylusHandwritingAvailableAsUser(@NonNull UserHandle user) {
         final Context fallbackContext = ActivityThread.currentApplication();
@@ -1655,6 +1658,7 @@
     @NonNull
     @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
     @TestApi
+    @FlaggedApi(Flags.FLAG_IMM_USERHANDLE_HOSTSIDETESTS)
     @SuppressLint("UserHandle")
     public List<InputMethodInfo> getEnabledInputMethodListAsUser(@NonNull UserHandle user) {
         return IInputMethodManagerGlobalInvoker.getEnabledInputMethodList(user.getIdentifier());
@@ -1690,12 +1694,13 @@
      *               {@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required if this is
      *               different from the calling process user ID.
      * @return {@link List} of {@link InputMethodSubtype}.
-     * @see #getEnabledInputMethodListAsUser(int)
+     * @see #getEnabledInputMethodListAsUser(UserHandle)
      * @hide
      */
     @NonNull
     @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
     @TestApi
+    @FlaggedApi(Flags.FLAG_IMM_USERHANDLE_HOSTSIDETESTS)
     @SuppressLint("UserHandle")
     public List<InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(
             @NonNull String imeId, boolean allowsImplicitlyEnabledSubtypes,
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index c14b510..1e8718c 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -14,4 +14,12 @@
     description: "Feature flag for adding EditorInfo#mStylusHandwritingEnabled"
     bug: "293898187"
     is_fixed_read_only: true
+}
+
+flag {
+    name: "imm_userhandle_hostsidetests"
+    namespace: "input_method"
+    description: "Feature flag for replacing UserIdInt with UserHandle in some helper IMM functions"
+    bug: "301713309"
+    is_fixed_read_only: true
 }
\ No newline at end of file
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 103725d..f19a2f9 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -685,8 +685,9 @@
             return false;
         }
 
+        /** See {@link RemoteViews#visitUris(Consumer)}. **/
         public void visitUris(@NonNull Consumer<Uri> visitor) {
-            // Nothing to visit by default
+            // Nothing to visit by default.
         }
     }
 
@@ -761,9 +762,11 @@
     }
 
     /**
-     * Note all {@link Uri} that are referenced internally, with the expectation
-     * that Uri permission grants will need to be issued to ensure the recipient
-     * of this object is able to render its contents.
+     * Note all {@link Uri} that are referenced internally, with the expectation that Uri permission
+     * grants will need to be issued to ensure the recipient of this object is able to render its
+     * contents.
+     * See b/281044385 for more context and examples about what happens when this isn't done
+     * correctly.
      *
      * @hide
      */
@@ -1088,6 +1091,13 @@
         public String getUniqueKey() {
             return (SET_REMOTE_ADAPTER_TAG + "_" + mViewId);
         }
+
+        @Override
+        public void visitUris(@NonNull Consumer<Uri> visitor) {
+            for (RemoteViews remoteViews : mList) {
+                remoteViews.visitUris(visitor);
+            }
+        }
     }
 
     /**
@@ -1289,6 +1299,12 @@
         public String getUniqueKey() {
             return (SET_REMOTE_ADAPTER_TAG + "_" + mViewId);
         }
+
+        @Override
+        public void visitUris(@NonNull Consumer<Uri> visitor) {
+            RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture);
+            items.visitUris(visitor);
+        }
     }
 
     private class SetRemoteViewsAdapterIntent extends Action {
@@ -1359,6 +1375,13 @@
         public int getActionTag() {
             return SET_REMOTE_VIEW_ADAPTER_INTENT_TAG;
         }
+
+        @Override
+        public void visitUris(@NonNull Consumer<Uri> visitor) {
+            // TODO(b/281044385): Maybe visit intent URIs. This may require adding a dedicated
+            //  visitUris method in the Intent class, since it can contain other intents. Otherwise,
+            //  the basic thing to do here would be just visitor.accept(intent.getData()).
+        }
     }
 
     /**
@@ -1434,6 +1457,11 @@
         public int getActionTag() {
             return SET_ON_CLICK_RESPONSE_TAG;
         }
+
+        @Override
+        public void visitUris(@NonNull Consumer<Uri> visitor) {
+            // TODO(b/281044385): Maybe visit intent URIs in the RemoteResponse.
+        }
     }
 
     /**
@@ -1504,6 +1532,11 @@
         public int getActionTag() {
             return SET_ON_CHECKED_CHANGE_RESPONSE_TAG;
         }
+
+        @Override
+        public void visitUris(@NonNull Consumer<Uri> visitor) {
+            // TODO(b/281044385): Maybe visit intent URIs in the RemoteResponse.
+        }
     }
 
     /** @hide **/
@@ -2063,6 +2096,7 @@
                     final Icon icon = (Icon) getParameterValue(null);
                     if (icon != null) visitIconUri(icon, visitor);
                     break;
+                // TODO(b/281044385): Should we do anything about type BUNDLE?
             }
         }
     }
@@ -2812,7 +2846,7 @@
         }
 
         @Override
-        public final void visitUris(@NonNull Consumer<Uri> visitor) {
+        public void visitUris(@NonNull Consumer<Uri> visitor) {
             mNestedViews.visitUris(visitor);
         }
     }
@@ -7262,6 +7296,15 @@
                         Math.max(mViewTypeCount, 1));
             }
         }
+
+        /**
+         * See {@link RemoteViews#visitUris(Consumer)}.
+         */
+        private void visitUris(@NonNull Consumer<Uri> visitor) {
+            for (RemoteViews view : mViews) {
+                view.visitUris(visitor);
+            }
+        }
     }
 
     /**
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index e8281ea..a0628c4 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -9854,7 +9854,10 @@
                 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
                 outAttrs.setInitialSurroundingText(mText);
                 outAttrs.contentMimeTypes = getReceiveContentMimeTypes();
-
+                if (android.view.inputmethod.Flags.editorinfoHandwritingEnabled()
+                        && isAutoHandwritingEnabled()) {
+                    outAttrs.setStylusHandwritingEnabled(true);
+                }
                 ArrayList<Class<? extends HandwritingGesture>> gestures = new ArrayList<>();
                 gestures.add(SelectGesture.class);
                 gestures.add(SelectRangeGesture.class);
@@ -15555,6 +15558,9 @@
     private void ensureIterableTextForAccessibilitySelectable() {
         if (!(mText instanceof Spannable)) {
             setText(mText, BufferType.SPANNABLE);
+            if (getLayout() == null) {
+                assumeLayout();
+            }
         }
     }
 
diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java
index eb9d62b..5d14698 100644
--- a/core/java/android/window/SurfaceSyncGroup.java
+++ b/core/java/android/window/SurfaceSyncGroup.java
@@ -263,7 +263,6 @@
             Trace.instantForTrack(Trace.TRACE_TAG_VIEW, mTrackName, "markSyncReady");
         }
         synchronized (mLock) {
-            toggleTimeout(false);
             if (mHasWMSync) {
                 try {
                     WindowManagerGlobal.getWindowManagerService().markSurfaceSyncGroupReady(mToken);
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 61f340a..1a2d202 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -402,6 +402,18 @@
 
     @Override
     public String toString() {
+        return toString("");
+    }
+
+    /**
+     * Returns a string representation of this transition info.
+     * @hide
+     */
+    public String toString(@NonNull String prefix) {
+        final boolean shouldPrettyPrint = !prefix.isEmpty() && !mChanges.isEmpty();
+        final String innerPrefix = shouldPrettyPrint ? prefix + "    " : "";
+        final String changesLineStart = shouldPrettyPrint ? "\n" + prefix : "";
+        final String perChangeLineStart = shouldPrettyPrint ? "\n" + innerPrefix : "";
         StringBuilder sb = new StringBuilder();
         sb.append("{id=").append(mDebugId).append(" t=").append(transitTypeToString(mType))
                 .append(" f=0x").append(Integer.toHexString(mFlags)).append(" trk=").append(mTrack)
@@ -413,12 +425,15 @@
             sb.append(mRoots.get(i).mDisplayId).append("@").append(mRoots.get(i).mOffset);
         }
         sb.append("] c=[");
+        sb.append(perChangeLineStart);
         for (int i = 0; i < mChanges.size(); ++i) {
             if (i > 0) {
                 sb.append(',');
+                sb.append(perChangeLineStart);
             }
             sb.append(mChanges.get(i));
         }
+        sb.append(changesLineStart);
         sb.append("]}");
         return sb.toString();
     }
diff --git a/core/java/android/window/TransitionRequestInfo.java b/core/java/android/window/TransitionRequestInfo.java
index 932608a3..bd54e14 100644
--- a/core/java/android/window/TransitionRequestInfo.java
+++ b/core/java/android/window/TransitionRequestInfo.java
@@ -62,13 +62,16 @@
     /** The transition flags known at the time of the request. These may not be complete. */
     private final int mFlags;
 
+    /** This is only a BEST-EFFORT id used for log correlation. DO NOT USE for any real work! */
+    private final int mDebugId;
+
     /** constructor override */
     public TransitionRequestInfo(
             @WindowManager.TransitionType int type,
             @Nullable ActivityManager.RunningTaskInfo triggerTask,
             @Nullable RemoteTransition remoteTransition) {
         this(type, triggerTask, null /* pipTask */,
-                remoteTransition, null /* displayChange */, 0 /* flags */);
+                remoteTransition, null /* displayChange */, 0 /* flags */, -1 /* debugId */);
     }
 
     /** constructor override */
@@ -78,16 +81,29 @@
             @Nullable RemoteTransition remoteTransition,
             int flags) {
         this(type, triggerTask, null /* pipTask */,
-                remoteTransition, null /* displayChange */, flags);
+                remoteTransition, null /* displayChange */, flags, -1 /* debugId */);
     }
 
+        /** constructor override */
     public TransitionRequestInfo(
             @WindowManager.TransitionType int type,
             @Nullable ActivityManager.RunningTaskInfo triggerTask,
             @Nullable RemoteTransition remoteTransition,
             @Nullable TransitionRequestInfo.DisplayChange displayChange,
             int flags) {
-        this(type, triggerTask, null /* pipTask */, remoteTransition, displayChange, flags);
+        this(type, triggerTask, null /* pipTask */, remoteTransition, displayChange, flags,
+                -1 /* debugId */);
+    }
+
+    /** constructor override */
+    public TransitionRequestInfo(
+            @WindowManager.TransitionType int type,
+            @Nullable ActivityManager.RunningTaskInfo triggerTask,
+            @Nullable ActivityManager.RunningTaskInfo pipTask,
+            @Nullable RemoteTransition remoteTransition,
+            @Nullable TransitionRequestInfo.DisplayChange displayChange,
+            int flags) {
+        this(type, triggerTask, pipTask, remoteTransition, displayChange, flags, -1 /* debugId */);
     }
 
     /** @hide */
@@ -270,7 +286,7 @@
         };
 
         @DataClass.Generated(
-                time = 1695667226050L,
+                time = 1697564781403L,
                 codegenVersion = "1.0.23",
                 sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java",
                 inputSignatures = "private final  int mDisplayId\nprivate @android.annotation.Nullable android.graphics.Rect mStartAbsBounds\nprivate @android.annotation.Nullable android.graphics.Rect mEndAbsBounds\nprivate  int mStartRotation\nprivate  int mEndRotation\nprivate  boolean mPhysicalDisplayChanged\nclass DisplayChange extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genBuilder=false, genConstructor=false)")
@@ -318,6 +334,8 @@
      *   (if size is changing).
      * @param flags
      *   The transition flags known at the time of the request. These may not be complete.
+     * @param debugId
+     *   This is only a BEST-EFFORT id used for log correlation. DO NOT USE for any real work!
      */
     @DataClass.Generated.Member
     public TransitionRequestInfo(
@@ -326,7 +344,8 @@
             @Nullable ActivityManager.RunningTaskInfo pipTask,
             @Nullable RemoteTransition remoteTransition,
             @Nullable TransitionRequestInfo.DisplayChange displayChange,
-            int flags) {
+            int flags,
+            int debugId) {
         this.mType = type;
         com.android.internal.util.AnnotationValidations.validate(
                 WindowManager.TransitionType.class, null, mType);
@@ -335,6 +354,7 @@
         this.mRemoteTransition = remoteTransition;
         this.mDisplayChange = displayChange;
         this.mFlags = flags;
+        this.mDebugId = debugId;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -392,6 +412,14 @@
     }
 
     /**
+     * This is only a BEST-EFFORT id used for log correlation. DO NOT USE for any real work!
+     */
+    @DataClass.Generated.Member
+    public int getDebugId() {
+        return mDebugId;
+    }
+
+    /**
      * If non-null, the task containing the activity whose lifecycle change (start or
      * finish) has caused this transition to occur.
      */
@@ -443,7 +471,8 @@
                 "pipTask = " + mPipTask + ", " +
                 "remoteTransition = " + mRemoteTransition + ", " +
                 "displayChange = " + mDisplayChange + ", " +
-                "flags = " + mFlags +
+                "flags = " + mFlags + ", " +
+                "debugId = " + mDebugId +
         " }";
     }
 
@@ -465,6 +494,7 @@
         if (mRemoteTransition != null) dest.writeTypedObject(mRemoteTransition, flags);
         if (mDisplayChange != null) dest.writeTypedObject(mDisplayChange, flags);
         dest.writeInt(mFlags);
+        dest.writeInt(mDebugId);
     }
 
     @Override
@@ -485,6 +515,7 @@
         RemoteTransition remoteTransition = (flg & 0x8) == 0 ? null : (RemoteTransition) in.readTypedObject(RemoteTransition.CREATOR);
         TransitionRequestInfo.DisplayChange displayChange = (flg & 0x10) == 0 ? null : (TransitionRequestInfo.DisplayChange) in.readTypedObject(TransitionRequestInfo.DisplayChange.CREATOR);
         int flags = in.readInt();
+        int debugId = in.readInt();
 
         this.mType = type;
         com.android.internal.util.AnnotationValidations.validate(
@@ -494,6 +525,7 @@
         this.mRemoteTransition = remoteTransition;
         this.mDisplayChange = displayChange;
         this.mFlags = flags;
+        this.mDebugId = debugId;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -513,10 +545,10 @@
     };
 
     @DataClass.Generated(
-            time = 1695667226088L,
+            time = 1697564781438L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java",
-            inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mPipTask\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.DisplayChange mDisplayChange\nprivate final  int mFlags\n  java.lang.String typeToString()\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)")
+            inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mPipTask\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.DisplayChange mDisplayChange\nprivate final  int mFlags\nprivate final  int mDebugId\n  java.lang.String typeToString()\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 392aa1b..79b3b4f 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -8,6 +8,14 @@
 }
 
 flag {
+    name: "defer_display_updates"
+    namespace: "window_manager"
+    description: "Feature flag for deferring DisplayManager updates to WindowManager if Shell transition is running"
+    bug: "259220649"
+    is_fixed_read_only: true
+}
+
+flag {
   name: "close_to_square_config_includes_status_bar"
   namespace: "windowing_frontend"
   description: "On close to square display, when necessary, configuration includes status bar"
@@ -18,6 +26,22 @@
   name: "dimmer_refactor"
   namespace: "windowing_frontend"
   description: "Refactor dim to fix flickers"
-  bug: "281632483,295291019"
+  bug: "295291019"
   is_fixed_read_only: true
+}
+
+flag {
+  name: "transit_ready_tracking"
+  namespace: "windowing_frontend"
+  description: "Enable accurate transition readiness tracking"
+  bug: "294925498"
+}
+
+
+flag {
+    name: "wallpaper_offset_async"
+    namespace: "windowing_frontend"
+    description: "Do not synchronise the wallpaper offset"
+    bug: "293248754"
+    is_fixed_read_only: true
 }
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/PlatLogoActivity.java b/core/java/com/android/internal/app/PlatLogoActivity.java
index 4e7bfe5..71bbccb 100644
--- a/core/java/com/android/internal/app/PlatLogoActivity.java
+++ b/core/java/com/android/internal/app/PlatLogoActivity.java
@@ -259,7 +259,7 @@
             }
             return true;
         }
-        return false;
+        return super.onKeyDown(keyCode,event);
     }
 
     @Override
@@ -268,7 +268,7 @@
             stopWarp();
             return true;
         }
-        return false;
+        return super.onKeyUp(keyCode,event);
     }
 
     private void startWarp() {
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index b1d22e0..77e1502 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -81,11 +81,6 @@
         public static final Flag PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS = releasedFlag(
                 "persist.sysui.notification.propagate_channel_updates_to_conversations");
 
-        // TODO: b/291907312 - remove feature flags
-        /** Gating the NMS->NotificationAttentionHelper buzzBeepBlink refactor */
-        public static final Flag ENABLE_ATTENTION_HELPER_REFACTOR = devFlag(
-                "persist.debug.sysui.notification.enable_attention_helper_refactor");
-
         // TODO b/291899544: for released flags, use resource config values
         /** Value used by polite notif. feature */
         public static final Flag NOTIF_COOLDOWN_T1 = devFlag(
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 6e836e0..7e9cef7 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -40,6 +40,7 @@
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNFOLD_ANIM;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_LAUNCH_CAMERA;
@@ -273,7 +274,9 @@
 
     public static final int CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER = 82;
 
-    private static final int LAST_CUJ = CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
+    public static final int CUJ_LAUNCHER_UNFOLD_ANIM = 83;
+
+    private static final int LAST_CUJ = CUJ_LAUNCHER_UNFOLD_ANIM;
     private static final int NO_STATSD_LOGGING = -1;
 
     // Used to convert CujType to InteractionType enum value for statsd logging.
@@ -366,6 +369,7 @@
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_SHOW_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_SHOW_ANIMATION;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_HIDE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_HIDE_ANIMATION;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_UNFOLD_ANIM] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNFOLD_ANIM;
     }
 
     private static class InstanceHolder {
@@ -468,6 +472,7 @@
             CUJ_IME_INSETS_SHOW_ANIMATION,
             CUJ_IME_INSETS_HIDE_ANIMATION,
             CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER,
+            CUJ_LAUNCHER_UNFOLD_ANIM,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {
@@ -1101,6 +1106,8 @@
                 return "IME_INSETS_HIDE_ANIMATION";
             case CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER:
                 return "SPLIT_SCREEN_DOUBLE_TAP_DIVIDER";
+            case CUJ_LAUNCHER_UNFOLD_ANIM:
+                return "LAUNCHER_UNFOLD_ANIM";
         }
         return "UNKNOWN";
     }
diff --git a/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java b/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java
index 6fd5018..504928c 100644
--- a/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java
+++ b/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java
@@ -74,16 +74,15 @@
         return windowSwipeToDismiss;
     }
 
-    private boolean isPointerIndexValid(MotionEvent ev) {
+    private int getIndexForValidPointer(MotionEvent ev) {
         int pointerIndex = ev.findPointerIndex(mActivePointerId);
         if (pointerIndex == -1) {
             if (DEBUG) {
                 Log.e(TAG, "Invalid pointer index: ignoring.");
             }
             mDiscardIntercept = true;
-            return false;
         }
-        return true;
+        return pointerIndex;
     }
 
     private void updateSwiping(MotionEvent ev) {
@@ -98,7 +97,7 @@
         }
     }
 
-    private void updateDiscardIntercept(MotionEvent ev) {
+    private void updateDiscardIntercept(MotionEvent ev, int pointerIndex) {
         if (!mSwiping) {
             // Don't look at canScroll until we have passed the touch slop
             return;
@@ -107,8 +106,8 @@
             return;
         }
         final boolean checkLeft = mDownX < ev.getRawX();
-        final float x = ev.getX(mActivePointerId);
-        final float y = ev.getY(mActivePointerId);
+        final float x = ev.getX(pointerIndex);
+        final float y = ev.getY(pointerIndex);
         if (canScroll(mInstalledDecorView, false, checkLeft, x, y)) {
             mDiscardIntercept = true;
         }
@@ -152,11 +151,12 @@
                 if (mDiscardIntercept) {
                     break;
                 }
-                if (!isPointerIndexValid(ev)) {
+                final int pointerIndex = getIndexForValidPointer(ev);
+                if (pointerIndex == -1) {
                     break;
                 }
                 updateSwiping(ev);
-                updateDiscardIntercept(ev);
+                updateDiscardIntercept(ev, pointerIndex);
                 break;
             case MotionEvent.ACTION_CANCEL:
             case MotionEvent.ACTION_UP:
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index 4065055..8236783 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -108,4 +108,5 @@
     boolean removeWeakEscrowToken(long handle, int userId);
     boolean isWeakEscrowTokenActive(long handle, int userId);
     boolean isWeakEscrowTokenValid(long handle, in byte[] token, int userId);
+    void unlockUserKeyIfUnsecured(int userId);
 }
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index d5b8f62..a3e2706 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1933,8 +1933,23 @@
         }
     }
 
+    /**
+     * Unlocks the credential-encrypted storage for the given user if the user is not secured, i.e.
+     * doesn't have an LSKF.
+     * <p>
+     * Whether the storage has been unlocked can be determined by
+     * {@link StorageManager#isUserKeyUnlocked()}.
+     *
+     * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission.
+     *
+     * @param userId the ID of the user whose storage to unlock
+     */
     public void unlockUserKeyIfUnsecured(@UserIdInt int userId) {
-        getLockSettingsInternal().unlockUserKeyIfUnsecured(userId);
+        try {
+            getLockSettings().unlockUserKeyIfUnsecured(userId);
+        } catch (RemoteException re) {
+            re.rethrowFromSystemServer();
+        }
     }
 
     public void createNewUser(@UserIdInt int userId, int userSerialNumber) {
diff --git a/core/java/com/android/internal/widget/LockSettingsInternal.java b/core/java/com/android/internal/widget/LockSettingsInternal.java
index 6063c90..8114e1f 100644
--- a/core/java/com/android/internal/widget/LockSettingsInternal.java
+++ b/core/java/com/android/internal/widget/LockSettingsInternal.java
@@ -60,17 +60,6 @@
     public abstract void onThirdPartyAppsStarted();
 
     /**
-     * Unlocks the credential-encrypted storage for the given user if the user is not secured, i.e.
-     * doesn't have an LSKF.
-     * <p>
-     * This doesn't throw an exception on failure; whether the storage has been unlocked can be
-     * determined by {@link StorageManager#isUserKeyUnlocked()}.
-     *
-     * @param userId the ID of the user whose storage to unlock
-     */
-    public abstract void unlockUserKeyIfUnsecured(@UserIdInt int userId);
-
-    /**
      * Creates the locksettings state for a new user.
      * <p>
      * This includes creating a synthetic password and protecting it with an empty LSKF.
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index deb138f..9c883d1 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -265,6 +265,18 @@
     return mgr->isDataInjectionEnabled();
 }
 
+static jboolean nativeIsReplayDataInjectionEnabled(JNIEnv *_env, jclass _this,
+                                                   jlong sensorManager) {
+    SensorManager *mgr = reinterpret_cast<SensorManager *>(sensorManager);
+    return mgr->isReplayDataInjectionEnabled();
+}
+
+static jboolean nativeIsHalBypassReplayDataInjectionEnabled(JNIEnv *_env, jclass _this,
+                                                            jlong sensorManager) {
+    SensorManager *mgr = reinterpret_cast<SensorManager *>(sensorManager);
+    return mgr->isHalBypassReplayDataInjectionEnabled();
+}
+
 static jint nativeCreateDirectChannel(JNIEnv *_env, jclass _this, jlong sensorManager,
                                       jint deviceId, jlong size, jint channelType, jint fd,
                                       jobject hardwareBufferObj) {
@@ -533,6 +545,11 @@
 
         {"nativeIsDataInjectionEnabled", "(J)Z", (void *)nativeIsDataInjectionEnabled},
 
+        {"nativeIsReplayDataInjectionEnabled", "(J)Z", (void *)nativeIsReplayDataInjectionEnabled},
+
+        {"nativeIsHalBypassReplayDataInjectionEnabled", "(J)Z",
+         (void *)nativeIsHalBypassReplayDataInjectionEnabled},
+
         {"nativeCreateDirectChannel", "(JIJIILandroid/hardware/HardwareBuffer;)I",
          (void *)nativeCreateDirectChannel},
 
diff --git a/core/proto/android/nfc/Android.bp b/core/proto/android/nfc/Android.bp
deleted file mode 100644
index 6a62c91..0000000
--- a/core/proto/android/nfc/Android.bp
+++ /dev/null
@@ -1,43 +0,0 @@
-//
-// Copyright (C) 2023 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-filegroup {
-    name: "srcs_nfc_proto",
-    srcs: [
-        "*.proto",
-    ],
-}
-
-// Will be statically linked by `framework-nfc`.
-java_library {
-    name: "nfc-proto-java-gen",
-    installable: false,
-    proto: {
-        type: "stream",
-        include_dirs: [
-            "external/protobuf/src",
-        ],
-    },
-    srcs: [
-        ":srcs_nfc_proto",
-    ],
-    sdk_version: "current",
-    min_sdk_version: "current",
-}
diff --git a/core/proto/android/service/package.proto b/core/proto/android/service/package.proto
index eb14db0..068f4dd 100644
--- a/core/proto/android/service/package.proto
+++ b/core/proto/android/service/package.proto
@@ -115,6 +115,9 @@
 
                 // Only set if the app defined a monochrome icon.
                 optional string monochrome_icon_bitmap_path = 3;
+
+                // The component name of the original activity (pre-archival).
+                optional string original_component_name = 4;
             }
 
             /** Information about main activities. */
diff --git a/core/res/Android.bp b/core/res/Android.bp
index b71995f..4e686b7 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -104,6 +104,7 @@
 
 android_app {
     name: "framework-res",
+    use_resource_processor: false,
     sdk_version: "core_platform",
     certificate: "platform",
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 88b578b..6daa5b9 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5668,7 +5668,8 @@
                 android:description="@string/permdesc_deliverCompanionMessages"
                 android:protectionLevel="normal" />
 
-    <!-- @hide @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES)
+    <!-- @hide @FlaggedApi("android.companion.flags.companion_transport_apis")
+         @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES)
          Allows an application to send and receive messages via CDM transports.
     -->
     <permission android:name="android.permission.USE_COMPANION_TRANSPORTS"
@@ -6123,7 +6124,7 @@
     <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
 
     <!-- @SystemApi @hide
-         @FlaggedApi("backstage_power.report_usage_stats_permission")
+         @FlaggedApi("android.app.usage.report_usage_stats_permission")
          Allows trusted system components to report events to UsageStatsManager -->
     <permission android:name="android.permission.REPORT_USAGE_STATS"
                 android:protectionLevel="signature|module" />
@@ -7143,7 +7144,7 @@
          {@link ActivityOptions#makeRemoteAnimation}
          @hide <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged|recents" />
 
     <!-- Allows an application to watch changes and/or active state of app ops.
          @hide <p>Not for use by third-party applications. -->
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 04fd70a..3496994 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -10090,6 +10090,15 @@
     <!-- Perceptual luminance of a color, in accessibility friendly color space. From 0 to 100. -->
     <attr name="lStar" format="float"/>
 
+    <!-- The attributes of the {@code <locale-config>} tag. -->
+    <!-- @FlaggedApi("android.content.res.default_locale") -->
+    <declare-styleable name="LocaleConfig">
+        <!-- The <a href="https://www.rfc-editor.org/rfc/bcp/bcp47.txt">IETF BCP47 language tag</a>
+       the strings in values/strings.xml (the default strings in the directory with no locale
+       qualifier) are in. -->
+        <attr name="defaultLocale" format="string"/>
+    </declare-styleable>
+
     <!-- The attributes of the {@code <locale>} tag within {@code <locale-config>}. -->
     <declare-styleable name="LocaleConfig_Locale">
         <!-- The <a href="https://www.rfc-editor.org/rfc/bcp/bcp47.txt">IETF BCP47 language tag</a>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index b211ac2..5a1f2d1a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3988,6 +3988,13 @@
          limit is unknown. -->
     <item name="config_hapticChannelMaxVibrationAmplitude" format="float" type="dimen">0</item>
 
+    <!-- The fixed keyboard vibration strength in [0,1], or -1 to indicate the strength not fixed
+         and should depend on the touch feedback intensity user setting -->
+    <item name="config_keyboardHapticFeedbackFixedAmplitude" format="float" type="dimen">-1</item>
+
+    <!-- The default value for keyboard vibration toggle in settings. -->
+    <bool name="config_defaultKeyboardVibrationEnabled">true</bool>
+
     <!-- If the device should still vibrate even in low power mode, for certain priority vibrations
      (e.g. accessibility, alarms). This is mainly for Wear devices that don't have speakers. -->
     <bool name="config_allowPriorityVibrationsInLowPowerMode">false</bool>
@@ -5541,6 +5548,14 @@
         <!-- Add packages here -->
     </string-array>
 
+    <!-- Enable pause wallpaper rendering upon state change such as app launch -->
+    <bool name="config_pauseWallpaperRenderWhenStateChangeEnabled">false</bool>
+
+    <!-- The list of packages to pause wallpaper rendering upon state change such as app launch -->
+    <string-array name="pause_wallpaper_render_when_state_change" translatable="false">
+        <!-- Add packages here -->
+    </string-array>
+
     <!-- Whether or not to hide the navigation bar when the soft keyboard is visible in order to
          create additional screen real estate outside beyond the keyboard. Note that the user needs
          to have a confirmed way to dismiss the keyboard when desired. -->
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index adc7fe0..4cd4f63 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -110,6 +110,8 @@
   <eat-comment/>
 
   <staging-public-group type="attr" first-id="0x01bd0000">
+    <!-- @FlaggedApi("android.content.res.default_locale") -->
+    <public name="defaultLocale"/>
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01bc0000">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 76744ea..8e1c09e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2053,6 +2053,8 @@
   <java-symbol type="integer" name="config_previousVibrationsDumpAggregationTimeMillisLimit" />
   <java-symbol type="integer" name="config_defaultVibrationAmplitude" />
   <java-symbol type="dimen" name="config_hapticChannelMaxVibrationAmplitude" />
+  <java-symbol type="dimen" name="config_keyboardHapticFeedbackFixedAmplitude" />
+  <java-symbol type="bool" name="config_defaultKeyboardVibrationEnabled" />
   <java-symbol type="integer" name="config_vibrationWaveformRampStepDuration" />
   <java-symbol type="bool" name="config_ignoreVibrationsOnWirelessCharger" />
   <java-symbol type="integer" name="config_vibrationWaveformRampDownDuration" />
@@ -4306,6 +4308,8 @@
   <java-symbol type="string" name="config_factoryResetPackage" />
   <java-symbol type="array" name="config_highRefreshRateBlacklist" />
   <java-symbol type="array" name="config_forceSlowJpegModeList" />
+  <java-symbol type="array" name="pause_wallpaper_render_when_state_change" />
+  <java-symbol type="bool" name="config_pauseWallpaperRenderWhenStateChangeEnabled" />
 
   <java-symbol type="array" name="config_smallAreaDetectionAllowlist" />
 
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index af8c69e..3a2e50a 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -54,6 +54,9 @@
     <!-- Azerbaijan: 4-5 digits, known premium codes listed -->
     <shortcode country="az" pattern="\\d{4,5}" premium="330[12]|87744|901[234]|93(?:94|101)|9426|9525" />
 
+    <!-- Bangladesh: 1-5 digits (standard system default, not country specific) -->
+    <shortcode country="bd" pattern="\\d{1,5}" free="16672" />
+
     <!-- Belgium: 4 digits, plus EU: http://www.mobileweb.be/en/mobileweb/sms-numberplan.asp -->
     <shortcode country="be" premium="\\d{4}" free="8\\d{3}|116\\d{3}" />
 
@@ -145,7 +148,7 @@
     <shortcode country="in" pattern="\\d{1,5}" free="59336|53969" />
 
     <!-- Indonesia: 1-5 digits (standard system default, not country specific) -->
-    <shortcode country="id" pattern="\\d{1,5}" free="99477|6006|46645|363" />
+    <shortcode country="id" pattern="\\d{1,5}" free="99477|6006|46645|363|93457" />
 
     <!-- Ireland: 5 digits, 5xxxx (50xxx=free, 5[12]xxx=standard), plus EU:
          http://www.comreg.ie/_fileupload/publications/ComReg1117.pdf -->
@@ -190,7 +193,7 @@
     <shortcode country="mk" pattern="\\d{1,6}" free="129005|122" />
 
     <!-- Mexico: 4-5 digits (not confirmed), known premium codes listed -->
-    <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963|91101" />
+    <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963|91101|45453" />
 
     <!-- Malaysia: 5 digits: http://www.skmm.gov.my/attachment/Consumer_Regulation/Mobile_Content_Services_FAQs.pdf -->
     <shortcode country="my" pattern="\\d{5}" premium="32298|33776" free="22099|28288|66668" />
@@ -205,7 +208,7 @@
     <shortcode country="no" pattern="\\d{4,5}" premium="2201|222[67]" free="2171" />
 
     <!-- New Zealand: 3-4 digits, known premium codes listed -->
-    <shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995|4679" free="1737|176|2141|3067|3068|3110|4006|4053|4061|4062|4202|4300|4334|4412|4575|5626|8006|8681" />
+    <shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995|4679" free="1737|176|2141|3067|3068|3110|3876|4006|4053|4061|4062|4202|4300|4334|4412|4575|5626|8006|8681" />
 
     <!-- Peru: 4-5 digits (not confirmed), known premium codes listed -->
     <shortcode country="pe" pattern="\\d{4,5}" free="9963|40778" />
diff --git a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
index 07dec5d..b843ad7 100644
--- a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
@@ -79,6 +79,8 @@
     private FaceManager.AuthenticationCallback mAuthCallback;
     @Mock
     private FaceManager.EnrollmentCallback mEnrollmentCallback;
+    @Mock
+    private FaceManager.FaceDetectionCallback mFaceDetectionCallback;
 
     @Captor
     private ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> mCaptor;
@@ -191,6 +193,23 @@
                 any(), anyString(), any(), any(), anyBoolean());
     }
 
+    @Test
+    public void detectClient_onError() throws RemoteException {
+        ArgumentCaptor<IFaceServiceReceiver> argumentCaptor =
+                ArgumentCaptor.forClass(IFaceServiceReceiver.class);
+
+        CancellationSignal cancellationSignal = new CancellationSignal();
+        mFaceManager.detectFace(cancellationSignal, mFaceDetectionCallback,
+                new FaceAuthenticateOptions.Builder().build());
+
+        verify(mService).detectFace(any(), argumentCaptor.capture(), any());
+
+        argumentCaptor.getValue().onError(5 /* error */, 0 /* vendorCode */);
+        mLooper.dispatchAll();
+
+        verify(mFaceDetectionCallback).onDetectionError(anyInt());
+    }
+
     private void initializeProperties() throws RemoteException {
         verify(mService).addAuthenticatorsRegisteredCallback(mCaptor.capture());
 
diff --git a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
index 625e2e3..70313b8 100644
--- a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
@@ -74,6 +74,8 @@
     private FingerprintManager.AuthenticationCallback mAuthCallback;
     @Mock
     private FingerprintManager.EnrollmentCallback mEnrollCallback;
+    @Mock
+    private FingerprintManager.FingerprintDetectionCallback mFingerprintDetectionCallback;
 
     @Captor
     private ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mCaptor;
@@ -166,4 +168,21 @@
                 anyString());
         verify(mService, never()).enroll(any(), any(), anyInt(), any(), anyString(), anyInt());
     }
+
+    @Test
+    public void detectClient_onError() throws RemoteException {
+        ArgumentCaptor<IFingerprintServiceReceiver> argumentCaptor =
+                ArgumentCaptor.forClass(IFingerprintServiceReceiver.class);
+
+        mFingerprintManager.detectFingerprint(new CancellationSignal(),
+                mFingerprintDetectionCallback,
+                new FingerprintAuthenticateOptions.Builder().build());
+
+        verify(mService).detectFingerprint(any(), argumentCaptor.capture(), any());
+
+        argumentCaptor.getValue().onError(5 /* error */, 0 /* vendorCode */);
+        mLooper.dispatchAll();
+
+        verify(mFingerprintDetectionCallback).onDetectionError(anyInt());
+    }
 }
diff --git a/core/tests/coretests/src/android/os/LocaleListTest.java b/core/tests/coretests/src/android/os/LocaleListTest.java
index 1f00a7a..88fc826 100644
--- a/core/tests/coretests/src/android/os/LocaleListTest.java
+++ b/core/tests/coretests/src/android/os/LocaleListTest.java
@@ -81,4 +81,49 @@
         // restore the original values
         LocaleList.setDefault(originalLocaleList, originalLocaleIndex);
     }
+
+    @SmallTest
+    public void testIntersection() {
+        LocaleList localesWithN = new LocaleList(
+                Locale.ENGLISH,
+                Locale.FRENCH,
+                Locale.GERMAN,
+                Locale.ITALIAN,
+                Locale.JAPANESE,
+                Locale.KOREAN,
+                Locale.CHINESE,
+                Locale.SIMPLIFIED_CHINESE,
+                Locale.TRADITIONAL_CHINESE,
+                Locale.FRANCE,
+                Locale.GERMANY,
+                Locale.JAPAN,
+                Locale.CANADA,
+                Locale.CANADA_FRENCH);
+        LocaleList localesWithE = new LocaleList(
+                Locale.ENGLISH,
+                Locale.FRENCH,
+                Locale.GERMAN,
+                Locale.JAPANESE,
+                Locale.KOREAN,
+                Locale.CHINESE,
+                Locale.SIMPLIFIED_CHINESE,
+                Locale.TRADITIONAL_CHINESE,
+                Locale.FRANCE,
+                Locale.GERMANY,
+                Locale.CANADA_FRENCH);
+        LocaleList localesWithNAndE = new LocaleList(
+                Locale.ENGLISH,
+                Locale.FRENCH,
+                Locale.GERMAN,
+                Locale.JAPANESE,
+                Locale.KOREAN,
+                Locale.CHINESE,
+                Locale.SIMPLIFIED_CHINESE,
+                Locale.TRADITIONAL_CHINESE,
+                Locale.FRANCE,
+                Locale.GERMANY,
+                Locale.CANADA_FRENCH);
+
+        assertEquals(localesWithNAndE, new LocaleList(localesWithE.getIntersection(localesWithN)));
+    }
 }
diff --git a/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
index 517aeae..0855268 100644
--- a/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
+++ b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
@@ -20,8 +20,6 @@
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE;
 
-import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM;
-
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
@@ -42,16 +40,12 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.SharedMemory;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.TestableContext;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.Flag;
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.FlagResolver;
-
-import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
@@ -71,6 +65,9 @@
 
     private NotificationChannel mNotificationChannel;
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     // TODO(b/284297289): remove this flag set once resolved.
     @Parameterized.Parameters(name = "rankingUpdateAshmem={0}")
     public static Boolean[] getRankingUpdateAshmem() {
@@ -424,30 +421,11 @@
         mNotificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, "test channel",
                 NotificationManager.IMPORTANCE_DEFAULT);
 
-        SystemUiSystemPropertiesFlags.TEST_RESOLVER = new FlagResolver() {
-            @Override
-            public boolean isEnabled(Flag flag) {
-                if (flag.mSysPropKey.equals(RANKING_UPDATE_ASHMEM.mSysPropKey)) {
-                    return mRankingUpdateAshmem;
-                }
-                return new SystemUiSystemPropertiesFlags.DebugResolver().isEnabled(flag);
-            }
-
-            @Override
-            public int getIntValue(Flag flag) {
-                return 0;
-            }
-
-            @Override
-            public String getStringValue(Flag flag) {
-                return null;
-            }
-        };
-    }
-
-    @After
-    public void tearDown() {
-        SystemUiSystemPropertiesFlags.TEST_RESOLVER = null;
+        if (mRankingUpdateAshmem) {
+            mSetFlagsRule.enableFlags(Flags.FLAG_RANKING_UPDATE_ASHMEM);
+        } else {
+            mSetFlagsRule.disableFlags(Flags.FLAG_RANKING_UPDATE_ASHMEM);
+        }
     }
 
     /**
@@ -497,8 +475,7 @@
         parcel.setDataPosition(0);
         NotificationRankingUpdate nru1 = NotificationRankingUpdate.CREATOR.createFromParcel(parcel);
         // The rankingUpdate file descriptor is only non-null in the new path.
-        if (SystemUiSystemPropertiesFlags.getResolver().isEnabled(
-                SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) {
+        if (Flags.rankingUpdateAshmem()) {
             assertTrue(nru1.isFdNotNullAndClosed());
         }
         detailedAssertEquals(nru, nru1);
@@ -636,7 +613,7 @@
 
     @Test
     public void testRankingUpdate_writesSmartActionToParcel() {
-        if (!mRankingUpdateAshmem) {
+        if (!Flags.rankingUpdateAshmem()) {
             return;
         }
         ArrayList<Notification.Action> actions = new ArrayList<>();
@@ -674,7 +651,7 @@
 
     @Test
     public void testRankingUpdate_handlesEmptySmartActionList() {
-        if (!mRankingUpdateAshmem) {
+        if (!Flags.rankingUpdateAshmem()) {
             return;
         }
         ArrayList<Notification.Action> actions = new ArrayList<>();
@@ -697,7 +674,7 @@
 
     @Test
     public void testRankingUpdate_handlesNullSmartActionList() {
-        if (!mRankingUpdateAshmem) {
+        if (!Flags.rankingUpdateAshmem()) {
             return;
         }
         NotificationListenerService.Ranking ranking =
diff --git a/core/tests/coretests/src/android/view/OWNERS b/core/tests/coretests/src/android/view/OWNERS
index 2ca9994..23668a4 100644
--- a/core/tests/coretests/src/android/view/OWNERS
+++ b/core/tests/coretests/src/android/view/OWNERS
@@ -20,4 +20,7 @@
 per-file *ScrollCapture*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS
 
 # Stylus
-per-file stylus/* = file:/core/java/android/text/OWNERS
\ No newline at end of file
+per-file stylus/* = file:/core/java/android/text/OWNERS
+
+# View
+file:/core/java/android/view/OWNERS
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java b/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java
index e117051..71bdce4 100644
--- a/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java
+++ b/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java
@@ -23,6 +23,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.eq;
@@ -148,6 +149,28 @@
         reporter.assertLastReportedSetEquals(sc5, sc6, sc7, sc8);
     }
 
+    @Test
+    public void testCallStackDebugging_matchesFilters() {
+        SurfaceControlRegistry registry = SurfaceControlRegistry.getProcessInstance();
+
+        // Specific name, any call
+        registry.setCallStackDebuggingParams("com.android.app1", "");
+        assertFalse(registry.matchesForCallStackDebugging("com.android.noMatchApp", "setAlpha"));
+        assertTrue(registry.matchesForCallStackDebugging("com.android.app1", "setAlpha"));
+
+        // Any name, specific call
+        registry.setCallStackDebuggingParams("", "setAlpha");
+        assertFalse(registry.matchesForCallStackDebugging("com.android.app1", "setLayer"));
+        assertTrue(registry.matchesForCallStackDebugging("com.android.app1", "setAlpha"));
+        assertTrue(registry.matchesForCallStackDebugging("com.android.app2", "setAlpha"));
+
+        // Specific name, specific call
+        registry.setCallStackDebuggingParams("com.android.app1", "setAlpha");
+        assertFalse(registry.matchesForCallStackDebugging("com.android.app1", "setLayer"));
+        assertFalse(registry.matchesForCallStackDebugging("com.android.app2", "setAlpha"));
+        assertTrue(registry.matchesForCallStackDebugging("com.android.app1", "setAlpha"));
+    }
+
     private SurfaceControl buildTestSurface() {
         return new SurfaceControl.Builder()
                 .setContainerLayer()
diff --git a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
index 86f26e5..df212eb 100644
--- a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
+++ b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
@@ -17,7 +17,9 @@
 package android.widget;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
+import android.content.Context;
 import android.platform.test.annotations.Presubmit;
 import android.util.PollingCheck;
 
@@ -32,6 +34,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 @RunWith(AndroidJUnit4.class)
 @MediumTest
 @Presubmit
@@ -49,23 +54,43 @@
     }
 
     @Test
-    public void testScrollAfterFlingTop() {
-        mHorizontalScrollView.scrollTo(100, 0);
-        mHorizontalScrollView.fling(-10000);
-        PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowLeft.getDistance() > 0);
-        PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowLeft.getDistance() == 0f);
+    public void testScrollAfterFlingLeft() throws Throwable {
+        WatchedEdgeEffect edgeEffect = new WatchedEdgeEffect(mActivity);
+        mHorizontalScrollView.mEdgeGlowLeft = edgeEffect;
+        mActivityRule.runOnUiThread(() -> mHorizontalScrollView.scrollTo(100, 0));
+        mActivityRule.runOnUiThread(() -> mHorizontalScrollView.fling(-10000));
+        assertTrue(edgeEffect.onAbsorbLatch.await(1, TimeUnit.SECONDS));
+        mActivityRule.runOnUiThread(() -> {}); // let the absorb takes effect -- least one frame
+        PollingCheck.waitFor(() -> edgeEffect.getDistance() == 0f);
         assertEquals(0, mHorizontalScrollView.getScrollX());
     }
 
     @Test
-    public void testScrollAfterFlingBottom() {
+    public void testScrollAfterFlingRight() throws Throwable {
+        WatchedEdgeEffect edgeEffect = new WatchedEdgeEffect(mActivity);
+        mHorizontalScrollView.mEdgeGlowRight = edgeEffect;
         int childWidth = mHorizontalScrollView.getChildAt(0).getWidth();
         int maxScroll = childWidth - mHorizontalScrollView.getWidth();
-        mHorizontalScrollView.scrollTo(maxScroll - 100, 0);
-        mHorizontalScrollView.fling(10000);
-        PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowRight.getDistance() > 0);
+        mActivityRule.runOnUiThread(() -> mHorizontalScrollView.scrollTo(maxScroll - 100, 0));
+        mActivityRule.runOnUiThread(() -> mHorizontalScrollView.fling(10000));
+        assertTrue(edgeEffect.onAbsorbLatch.await(1, TimeUnit.SECONDS));
+        mActivityRule.runOnUiThread(() -> {}); // let the absorb takes effect -- at least one frame
         PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowRight.getDistance() == 0f);
         assertEquals(maxScroll, mHorizontalScrollView.getScrollX());
     }
+
+    static class WatchedEdgeEffect extends EdgeEffect {
+        public CountDownLatch onAbsorbLatch = new CountDownLatch(1);
+
+        WatchedEdgeEffect(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onAbsorb(int velocity) {
+            super.onAbsorb(velocity);
+            onAbsorbLatch.countDown();
+        }
+    }
 }
 
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index b71aaf3..cc73ece 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -2887,6 +2887,12 @@
       "group": "WM_DEBUG_RESIZE",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
+    "419378610": {
+      "message": "Content Recording: Apply transformations of shift %d x %d, scale %f x %f, crop (aka recorded content size) %d x %d for display %d; display has size %d x %d; surface has size %d x %d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONTENT_RECORDING",
+      "at": "com\/android\/server\/wm\/ContentRecorder.java"
+    },
     "422634333": {
       "message": "First draw done in potential wallpaper target %s",
       "level": "VERBOSE",
@@ -4339,12 +4345,6 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
-    "1936800105": {
-      "message": "Content Recording: Apply transformations of shift %d x %d, scale %f, crop (aka recorded content size) %d x %d for display %d; display has size %d x %d; surface has size %d x %d",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_CONTENT_RECORDING",
-      "at": "com\/android\/server\/wm\/ContentRecorder.java"
-    },
     "1945495497": {
       "message": "Focused window didn't have a valid surface drawn.",
       "level": "DEBUG",
diff --git a/graphics/java/android/framework_graphics.aconfig b/graphics/java/android/framework_graphics.aconfig
index e030dad..9a0a22a 100644
--- a/graphics/java/android/framework_graphics.aconfig
+++ b/graphics/java/android/framework_graphics.aconfig
@@ -2,7 +2,7 @@
 
 flag {
      name: "exact_compute_bounds"
-     namespace: "framework_graphics"
+     namespace: "core_graphics"
      description: "Add a function without unused exact param for computeBounds."
      bug: "304478551"
 }
\ No newline at end of file
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index f5e5803..dc1773b 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -39,12 +39,14 @@
     /**
      * No hyphenation preference is specified.
      *
+     * <p>
      * This is a special value of hyphenation preference indicating no hyphenation preference is
      * specified. When overriding a {@link LineBreakConfig} with another {@link LineBreakConfig}
      * with {@link Builder#merge(LineBreakConfig)} function, the hyphenation preference of
      * overridden config will be kept if the hyphenation preference of overriding config is
      * {@link #HYPHENATION_UNSPECIFIED}.
      *
+     * <p>
      * <pre>
      *     val override = LineBreakConfig.Builder()
      *          .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE)
@@ -57,6 +59,7 @@
      *     // LINE_BREAK_WORD_STYLE_PHRASE for line break word style.
      * </pre>
      *
+     * <p>
      * This value is resolved to {@link #HYPHENATION_ENABLED} if this value is used for text
      * layout/rendering.
      */
@@ -89,6 +92,7 @@
     /**
      * No line break style is specified.
      *
+     * <p>
      * This is a special value of line break style indicating no style value is specified.
      * When overriding a {@link LineBreakConfig} with another {@link LineBreakConfig} with
      * {@link Builder#merge(LineBreakConfig)} function, the line break style of overridden config
@@ -107,6 +111,7 @@
      *     // LINE_BREAK_WORD_STYLE_PHRASE for line break word style.
      * </pre>
      *
+     * <p>
      * This value is resolved to {@link #LINE_BREAK_STYLE_NONE} if this value is used for text
      * layout/rendering.
      */
@@ -270,6 +275,8 @@
         /**
          * Resets this builder to the given config state.
          *
+         * @param config a config value used for resetting. {@code null} is allowed. If {@code null}
+         *              is passed, all configs are reset to unspecified.
          * @return This {@code Builder}.
          * @hide
          */
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index c72a42c..18796494 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -151,6 +151,7 @@
     static_libs: [
         "androidx.appcompat_appcompat",
         "androidx.core_core-animation",
+        "androidx.core_core-ktx",
         "androidx.arch.core_core-runtime",
         "androidx-constraintlayout_constraintlayout",
         "androidx.dynamicanimation_dynamicanimation",
diff --git a/libs/WindowManager/Shell/res/values-television/config.xml b/libs/WindowManager/Shell/res/values-television/config.xml
index da8abde..8d2e28b 100644
--- a/libs/WindowManager/Shell/res/values-television/config.xml
+++ b/libs/WindowManager/Shell/res/values-television/config.xml
@@ -45,13 +45,13 @@
     <integer name="config_pipForceCloseDelay">5000</integer>
 
     <!-- Animation duration when exit starting window: fade out icon -->
-    <integer name="starting_window_app_reveal_icon_fade_out_duration">500</integer>
+    <integer name="starting_window_app_reveal_icon_fade_out_duration">200</integer>
 
     <!-- Animation delay when exit starting window: reveal app -->
-    <integer name="starting_window_app_reveal_anim_delay">0</integer>
+    <integer name="starting_window_app_reveal_anim_delay">200</integer>
 
     <!-- Animation duration when exit starting window: reveal app -->
-    <integer name="starting_window_app_reveal_anim_duration">500</integer>
+    <integer name="starting_window_app_reveal_anim_duration">300</integer>
 
     <!-- Default animation type when hiding the starting window. The possible values are:
           - 0 for radial vanish + slide up
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 2241c34..ac5ba51e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -1784,13 +1784,14 @@
             mStackOnLeftOrWillBe = mPositioner.isStackOnLeft(startPosition);
             mStackAnimationController.setStackPosition(startPosition);
             mExpandedAnimationController.setCollapsePoint(startPosition);
-            // Set the translation x so that this bubble will animate in from the same side they
-            // expand / collapse on.
-            bubble.getIconView().setTranslationX(startPosition.x);
         } else if (firstBubble) {
             mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
         }
 
+        // Set the view translation x so that this bubble will animate in from the same side they
+        // expand / collapse on.
+        bubble.getIconView().setTranslationX(mStackAnimationController.getStackPosition().x);
+
         mBubbleContainer.addView(bubble.getIconView(), 0,
                 new FrameLayout.LayoutParams(mPositioner.getBubbleSize(),
                         mPositioner.getBubbleSize()));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
index 738c94e..79f306e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
@@ -19,6 +19,7 @@
 import static android.view.View.LAYOUT_DIRECTION_RTL;
 
 import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
+import static com.android.wm.shell.bubbles.animation.FlingToDismissUtils.getFlingToDismissTargetWidth;
 
 import android.content.res.Resources;
 import android.graphics.Path;
@@ -375,6 +376,9 @@
         mMagnetizedBubbleDraggingOut.setMagnetListener(listener);
         mMagnetizedBubbleDraggingOut.setHapticsEnabled(true);
         mMagnetizedBubbleDraggingOut.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
+        int screenWidthPx = mLayout.getContext().getResources().getDisplayMetrics().widthPixels;
+        mMagnetizedBubbleDraggingOut.setFlingToTargetWidthPercent(
+                getFlingToDismissTargetWidth(screenWidthPx));
     }
 
     private void springBubbleTo(View bubble, float x, float y) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/FlingToDismissUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/FlingToDismissUtils.kt
new file mode 100644
index 0000000..2a44f04
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/FlingToDismissUtils.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.wm.shell.bubbles.animation
+
+/** Utils related to the fling to dismiss animation. */
+object FlingToDismissUtils {
+
+    /** The target width surrounding the dismiss target on a small width screen, e.g. phone. */
+    private const val FLING_TO_DISMISS_TARGET_WIDTH_SMALL = 3f
+    /**
+     * The target width surrounding the dismiss target on a medium width screen, e.g. tablet in
+     * portrait.
+     */
+    private const val FLING_TO_DISMISS_TARGET_WIDTH_MEDIUM = 4.5f
+    /**
+     * The target width surrounding the dismiss target on a large width screen, e.g. tablet in
+     * landscape.
+     */
+    private const val FLING_TO_DISMISS_TARGET_WIDTH_LARGE = 6f
+
+    /** Returns the dismiss target width for the specified [screenWidthPx]. */
+    @JvmStatic
+    fun getFlingToDismissTargetWidth(screenWidthPx: Int) = when {
+        screenWidthPx >= 2000 -> FLING_TO_DISMISS_TARGET_WIDTH_LARGE
+        screenWidthPx >= 1500 -> FLING_TO_DISMISS_TARGET_WIDTH_MEDIUM
+        else -> FLING_TO_DISMISS_TARGET_WIDTH_SMALL
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index aad2683..e487328 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -17,6 +17,7 @@
 package com.android.wm.shell.bubbles.animation;
 
 import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
+import static com.android.wm.shell.bubbles.animation.FlingToDismissUtils.getFlingToDismissTargetWidth;
 
 import android.content.ContentResolver;
 import android.content.res.Resources;
@@ -851,6 +852,15 @@
         if (mLayout != null) {
             Resources res = mLayout.getContext().getResources();
             mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
+            updateFlingToDismissTargetWidth();
+        }
+    }
+
+    private void updateFlingToDismissTargetWidth() {
+        if (mLayout != null && mMagnetizedStack != null) {
+            int screenWidthPx = mLayout.getResources().getDisplayMetrics().widthPixels;
+            mMagnetizedStack.setFlingToTargetWidthPercent(
+                    getFlingToDismissTargetWidth(screenWidthPx));
         }
     }
 
@@ -1022,23 +1032,8 @@
             };
             mMagnetizedStack.setHapticsEnabled(true);
             mMagnetizedStack.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
+            updateFlingToDismissTargetWidth();
         }
-
-        final ContentResolver contentResolver = mLayout.getContext().getContentResolver();
-        final float minVelocity = Settings.Secure.getFloat(contentResolver,
-                "bubble_dismiss_fling_min_velocity",
-                mMagnetizedStack.getFlingToTargetMinVelocity() /* default */);
-        final float maxVelocity = Settings.Secure.getFloat(contentResolver,
-                "bubble_dismiss_stick_max_velocity",
-                mMagnetizedStack.getStickToTargetMaxXVelocity() /* default */);
-        final float targetWidth = Settings.Secure.getFloat(contentResolver,
-                "bubble_dismiss_target_width_percent",
-                mMagnetizedStack.getFlingToTargetWidthPercent() /* default */);
-
-        mMagnetizedStack.setFlingToTargetMinVelocity(minVelocity);
-        mMagnetizedStack.setStickToTargetMaxXVelocity(maxVelocity);
-        mMagnetizedStack.setFlingToTargetWidthPercent(targetWidth);
-
         return mMagnetizedStack;
     }
 
@@ -1053,7 +1048,7 @@
      * property directly to move the first bubble and cause the stack to 'follow' to the new
      * location.
      *
-     * This could also be achieved by simply animating the first bubble view and adding an update
+     * <p>This could also be achieved by simply animating the first bubble view and adding an update
      * listener to dispatch movement to the rest of the stack. However, this would require
      * duplication of logic in that update handler - it's simpler to keep all logic contained in the
      * {@link #moveFirstBubbleWithStackFollowing} method.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index c51af46..ea7b2e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -605,6 +605,7 @@
     @Provides
     static Transitions provideTransitions(Context context,
             ShellInit shellInit,
+            ShellCommandHandler shellCommandHandler,
             ShellController shellController,
             ShellTaskOrganizer organizer,
             TransactionPool pool,
@@ -612,14 +613,13 @@
             @ShellMainThread ShellExecutor mainExecutor,
             @ShellMainThread Handler mainHandler,
             @ShellAnimationThread ShellExecutor animExecutor,
-            ShellCommandHandler shellCommandHandler,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
         if (!context.getResources().getBoolean(R.bool.config_registerShellTransitionsOnInit)) {
             // TODO(b/238217847): Force override shell init if registration is disabled
             shellInit = new ShellInit(mainExecutor);
         }
-        return new Transitions(context, shellInit, shellController, organizer, pool,
-                displayController, mainExecutor, mainHandler, animExecutor, shellCommandHandler,
+        return new Transitions(context, shellInit, shellCommandHandler, shellController, organizer,
+                pool, displayController, mainExecutor, mainHandler, animExecutor,
                 rootTaskDisplayAreaOrganizer);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/OWNERS
new file mode 100644
index 0000000..74a29dd
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/OWNERS
@@ -0,0 +1 @@
+hwwang@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 8a64037..1898ea7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -17,19 +17,28 @@
 package com.android.wm.shell.dagger.pip;
 
 import android.annotation.NonNull;
+import android.content.Context;
 
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.dagger.WMShellBaseModule;
 import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.pip2.phone.PipController;
 import com.android.wm.shell.pip2.phone.PipTransition;
+import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 
 import dagger.Module;
 import dagger.Provides;
 
+import java.util.Optional;
+
 /**
  * Provides dependencies from {@link com.android.wm.shell.pip2}, this implementation is meant to be
  * the successor of its sibling {@link Pip1Module}.
@@ -42,8 +51,26 @@
             @NonNull ShellTaskOrganizer shellTaskOrganizer,
             @NonNull Transitions transitions,
             PipBoundsState pipBoundsState,
-            PipBoundsAlgorithm pipBoundsAlgorithm) {
+            PipBoundsAlgorithm pipBoundsAlgorithm,
+            Optional<PipController> pipController) {
         return new PipTransition(shellInit, shellTaskOrganizer, transitions, pipBoundsState, null,
                 pipBoundsAlgorithm);
     }
+
+    @WMSingleton
+    @Provides
+    static Optional<PipController> providePipController(Context context,
+            ShellInit shellInit,
+            ShellController shellController,
+            DisplayController displayController,
+            DisplayInsetsController displayInsetsController,
+            PipDisplayLayoutState pipDisplayLayoutState) {
+        if (!PipUtils.isPip2ExperimentEnabled()) {
+            return Optional.empty();
+        } else {
+            return Optional.ofNullable(PipController.create(
+                    context, shellInit, shellController, displayController, displayInsetsController,
+                    pipDisplayLayoutState));
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
new file mode 100644
index 0000000..186cb61
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -0,0 +1,133 @@
+/*
+ * 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.wm.shell.pip2.phone;
+
+import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.view.InsetsState;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ConfigurationChangeListener;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
+
+/**
+ * Manages the picture-in-picture (PIP) UI and states for Phones.
+ */
+public class PipController implements ConfigurationChangeListener,
+        DisplayController.OnDisplaysChangedListener {
+    private static final String TAG = PipController.class.getSimpleName();
+
+    private Context mContext;
+    private ShellController mShellController;
+    private DisplayController mDisplayController;
+    private DisplayInsetsController mDisplayInsetsController;
+    private PipDisplayLayoutState mPipDisplayLayoutState;
+
+    private PipController(Context context,
+            ShellInit shellInit,
+            ShellController shellController,
+            DisplayController displayController,
+            DisplayInsetsController displayInsetsController,
+            PipDisplayLayoutState pipDisplayLayoutState) {
+        mContext = context;
+        mShellController = shellController;
+        mDisplayController = displayController;
+        mDisplayInsetsController = displayInsetsController;
+        mPipDisplayLayoutState = pipDisplayLayoutState;
+
+        if (PipUtils.isPip2ExperimentEnabled()) {
+            shellInit.addInitCallback(this::onInit, this);
+        }
+    }
+
+    private void onInit() {
+        // Ensure that we have the display info in case we get calls to update the bounds before the
+        // listener calls back
+        mPipDisplayLayoutState.setDisplayId(mContext.getDisplayId());
+        DisplayLayout layout = new DisplayLayout(mContext, mContext.getDisplay());
+        mPipDisplayLayoutState.setDisplayLayout(layout);
+
+        mShellController.addConfigurationChangeListener(this);
+        mDisplayController.addDisplayWindowListener(this);
+        mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(),
+                new DisplayInsetsController.OnInsetsChangedListener() {
+                    @Override
+                    public void insetsChanged(InsetsState insetsState) {
+                        onDisplayChanged(mDisplayController
+                                        .getDisplayLayout(mPipDisplayLayoutState.getDisplayId()));
+                    }
+                });
+    }
+
+    /**
+     * Instantiates {@link PipController}, returns {@code null} if the feature not supported.
+     */
+    public static PipController create(Context context,
+            ShellInit shellInit,
+            ShellController shellController,
+            DisplayController displayController,
+            DisplayInsetsController displayInsetsController,
+            PipDisplayLayoutState pipDisplayLayoutState) {
+        if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
+            ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: Device doesn't support Pip feature", TAG);
+            return null;
+        }
+        return new PipController(context, shellInit, shellController, displayController,
+                displayInsetsController, pipDisplayLayoutState);
+    }
+
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfiguration) {
+        mPipDisplayLayoutState.onConfigurationChanged();
+    }
+
+    @Override
+    public void onThemeChanged() {
+        onDisplayChanged(new DisplayLayout(mContext, mContext.getDisplay()));
+    }
+
+    @Override
+    public void onDisplayAdded(int displayId) {
+        if (displayId != mPipDisplayLayoutState.getDisplayId()) {
+            return;
+        }
+        onDisplayChanged(mDisplayController.getDisplayLayout(displayId));
+    }
+
+    @Override
+    public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+        if (displayId != mPipDisplayLayoutState.getDisplayId()) {
+            return;
+        }
+        onDisplayChanged(mDisplayController.getDisplayLayout(displayId));
+    }
+
+    private void onDisplayChanged(DisplayLayout layout) {
+        mPipDisplayLayoutState.setDisplayLayout(layout);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index 8b050e5..b1fc16d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -63,7 +63,7 @@
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         if (mTransition != transition) return false;
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Using registered One-shot remote"
-                + " transition %s for #%d.", mRemote, info.getDebugId());
+                + " transition %s for (#%d).", mRemote, info.getDebugId());
 
         final IBinder.DeathRecipient remoteDied = () -> {
             Log.e(Transitions.TAG, "Remote transition died, finishing");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index 592b22a..ca2c3b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -126,7 +126,7 @@
                 }
             }
         }
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Delegate animation for #%d to %s",
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Delegate animation for (#%d) to %s",
                 info.getDebugId(), pendingRemote);
 
         if (pendingRemote == null) return false;
@@ -241,7 +241,7 @@
         if (remote == null) return null;
         mRequestedRemotes.put(transition, remote);
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "RemoteTransition directly requested"
-                + " for %s: %s", transition, remote);
+                + " for (#%d) %s: %s", request.getDebugId(), transition, remote);
         return new WindowContainerTransaction();
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 0d9a9e9..576bba96 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -179,6 +179,7 @@
     private final DefaultTransitionHandler mDefaultTransitionHandler;
     private final RemoteTransitionHandler mRemoteTransitionHandler;
     private final DisplayController mDisplayController;
+    private final ShellCommandHandler mShellCommandHandler;
     private final ShellController mShellController;
     private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
     private final SleepHandler mSleepHandler = new SleepHandler();
@@ -188,9 +189,6 @@
     /** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
     private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>();
 
-    @Nullable
-    private final ShellCommandHandler mShellCommandHandler;
-
     private final ArrayList<TransitionObserver> mObservers = new ArrayList<>();
 
     /** List of {@link Runnable} instances to run when the last active transition has finished.  */
@@ -237,7 +235,7 @@
         @Override
         public String toString() {
             if (mInfo != null && mInfo.getDebugId() >= 0) {
-                return "(#" + mInfo.getDebugId() + ")" + mToken + "@" + getTrack();
+                return "(#" + mInfo.getDebugId() + ") " + mToken + "@" + getTrack();
             }
             return mToken.toString() + "@" + getTrack();
         }
@@ -275,13 +273,14 @@
             @NonNull ShellExecutor mainExecutor,
             @NonNull Handler mainHandler,
             @NonNull ShellExecutor animExecutor) {
-        this(context, shellInit, shellController, organizer, pool, displayController, mainExecutor,
-                mainHandler, animExecutor, null,
+        this(context, shellInit, new ShellCommandHandler(), shellController, organizer, pool,
+                displayController, mainExecutor, mainHandler, animExecutor,
                 new RootTaskDisplayAreaOrganizer(mainExecutor, context, shellInit));
     }
 
     public Transitions(@NonNull Context context,
             @NonNull ShellInit shellInit,
+            @Nullable ShellCommandHandler shellCommandHandler,
             @NonNull ShellController shellController,
             @NonNull WindowOrganizer organizer,
             @NonNull TransactionPool pool,
@@ -289,7 +288,6 @@
             @NonNull ShellExecutor mainExecutor,
             @NonNull Handler mainHandler,
             @NonNull ShellExecutor animExecutor,
-            @Nullable ShellCommandHandler shellCommandHandler,
             @NonNull RootTaskDisplayAreaOrganizer rootTDAOrganizer) {
         mOrganizer = organizer;
         mContext = context;
@@ -300,13 +298,13 @@
         mDefaultTransitionHandler = new DefaultTransitionHandler(context, shellInit,
                 displayController, pool, mainExecutor, mainHandler, animExecutor, rootTDAOrganizer);
         mRemoteTransitionHandler = new RemoteTransitionHandler(mMainExecutor);
+        mShellCommandHandler = shellCommandHandler;
         mShellController = shellController;
         // The very last handler (0 in the list) should be the default one.
         mHandlers.add(mDefaultTransitionHandler);
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Default");
         // Next lowest priority is remote transitions.
         mHandlers.add(mRemoteTransitionHandler);
-        mShellCommandHandler = shellCommandHandler;
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote");
         shellInit.addInitCallback(this::onInit, this);
     }
@@ -339,9 +337,8 @@
             TransitionMetrics.getInstance();
         }
 
-        if (mShellCommandHandler != null) {
-            mShellCommandHandler.addCommandCallback("transitions", this, this);
-        }
+        mShellCommandHandler.addCommandCallback("transitions", this, this);
+        mShellCommandHandler.addDumpCallback(this::dump, this);
     }
 
     public boolean isRegistered() {
@@ -655,8 +652,8 @@
     void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
         info.setUnreleasedWarningCallSiteForAllSurfaces("Transitions.onTransitionReady");
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s",
-                transitionToken, info);
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady (#%d) %s: %s",
+                info.getDebugId(), transitionToken, info);
         final int activeIdx = findByToken(mPendingTransitions, transitionToken);
         if (activeIdx < 0) {
             throw new IllegalStateException("Got transitionReady for non-pending transition "
@@ -1073,8 +1070,8 @@
 
     void requestStartTransition(@NonNull IBinder transitionToken,
             @Nullable TransitionRequestInfo request) {
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: %s %s",
-                transitionToken, request);
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested (#%d): %s %s",
+                request.getDebugId(), transitionToken, request);
         if (isTransitionKnown(transitionToken)) {
             throw new RuntimeException("Transition already started " + transitionToken);
         }
@@ -1475,4 +1472,68 @@
         pw.println(prefix + "tracing");
         mTracer.printShellCommandHelp(pw, prefix + "  ");
     }
+
+    private void dump(@NonNull PrintWriter pw, String prefix) {
+        pw.println(prefix + TAG);
+
+        final String innerPrefix = prefix + "  ";
+        pw.println(prefix + "Handlers:");
+        for (TransitionHandler handler : mHandlers) {
+            pw.print(innerPrefix);
+            pw.print(handler.getClass().getSimpleName());
+            pw.println(" (" + Integer.toHexString(System.identityHashCode(handler)) + ")");
+        }
+
+        pw.println(prefix + "Observers:");
+        for (TransitionObserver observer : mObservers) {
+            pw.print(innerPrefix);
+            pw.println(observer.getClass().getSimpleName());
+        }
+
+        pw.println(prefix + "Pending Transitions:");
+        for (ActiveTransition transition : mPendingTransitions) {
+            pw.print(innerPrefix + "token=");
+            pw.println(transition.mToken);
+            pw.print(innerPrefix + "id=");
+            pw.println(transition.mInfo != null
+                    ? transition.mInfo.getDebugId()
+                    : -1);
+            pw.print(innerPrefix + "handler=");
+            pw.println(transition.mHandler != null
+                    ? transition.mHandler.getClass().getSimpleName()
+                    : null);
+        }
+        if (mPendingTransitions.isEmpty()) {
+            pw.println(innerPrefix + "none");
+        }
+
+        pw.println(prefix + "Ready-during-sync Transitions:");
+        for (ActiveTransition transition : mReadyDuringSync) {
+            pw.print(innerPrefix + "token=");
+            pw.println(transition.mToken);
+            pw.print(innerPrefix + "id=");
+            pw.println(transition.mInfo != null
+                    ? transition.mInfo.getDebugId()
+                    : -1);
+            pw.print(innerPrefix + "handler=");
+            pw.println(transition.mHandler != null
+                    ? transition.mHandler.getClass().getSimpleName()
+                    : null);
+        }
+        if (mReadyDuringSync.isEmpty()) {
+            pw.println(innerPrefix + "none");
+        }
+
+        pw.println(prefix + "Tracks:");
+        for (int i = 0; i < mTracks.size(); i++) {
+            final ActiveTransition transition = mTracks.get(i).mActiveTransition;
+            pw.println(innerPrefix + "Track #" + i);
+            pw.print(innerPrefix + "active=");
+            pw.println(transition);
+            if (transition != null) {
+                pw.print(innerPrefix + "hander=");
+                pw.println(transition.mHandler);
+            }
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index 389db62..dadd264 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -18,6 +18,7 @@
 
 import android.graphics.PointF;
 import android.graphics.Rect;
+import android.view.Surface;
 import android.view.SurfaceControl;
 import android.window.WindowContainerTransaction;
 
@@ -45,6 +46,7 @@
     private final int mDisallowedAreaForEndBoundsHeight;
     private boolean mHasDragResized;
     private int mCtrlType;
+    @Surface.Rotation private int mRotation;
 
     FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
             DisplayController displayController, int disallowedAreaForEndBoundsHeight) {
@@ -78,7 +80,10 @@
             mTaskOrganizer.applyTransaction(wct);
         }
         mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
-        if (mStableBounds.isEmpty()) {
+        int rotation = mWindowDecoration
+                .mTaskInfo.configuration.windowConfiguration.getDisplayRotation();
+        if (mStableBounds.isEmpty() || mRotation != rotation) {
+            mRotation = rotation;
             mDisplayController.getDisplayLayout(mWindowDecoration.mDisplay.getDisplayId())
                     .getStableBounds(mStableBounds);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index 303954a..852c037 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -21,6 +21,7 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.IBinder;
+import android.view.Surface;
 import android.view.SurfaceControl;
 import android.window.TransitionInfo;
 import android.window.TransitionRequestInfo;
@@ -58,6 +59,7 @@
     private final int mDisallowedAreaForEndBoundsHeight;
     private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
     private int mCtrlType;
+    @Surface.Rotation private int mRotation;
 
     public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
             DesktopModeWindowDecoration windowDecoration, DisplayController displayController,
@@ -98,7 +100,10 @@
         }
         mDragStartListener.onDragStart(mDesktopWindowDecoration.mTaskInfo.taskId);
         mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
-        if (mStableBounds.isEmpty()) {
+        int rotation = mDesktopWindowDecoration
+                .mTaskInfo.configuration.windowConfiguration.getDisplayRotation();
+        if (mStableBounds.isEmpty() || mRotation != rotation) {
+            mRotation = rotation;
             mDisplayController.getDisplayLayout(mDesktopWindowDecoration.mDisplay.getDisplayId())
                     .getStableBounds(mStableBounds);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 0548a8e..d0e647b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -297,7 +297,7 @@
         }
 
         // Task surface itself
-        float shadowRadius = loadDimension(resources, params.mShadowRadiusId);
+        float shadowRadius;
         final Point taskPosition = mTaskInfo.positionInParent;
         if (isFullscreen) {
             // Setting the task crop to the width/height stops input events from being sent to
@@ -308,9 +308,12 @@
             // drag-resized by the window decoration.
             startT.setWindowCrop(mTaskSurface, null);
             finishT.setWindowCrop(mTaskSurface, null);
+            // Shadow is not needed for fullscreen tasks
+            shadowRadius = 0;
         } else {
             startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
             finishT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
+            shadowRadius = loadDimension(resources, params.mShadowRadiusId);
         }
         startT.setShadowRadius(mTaskSurface, shadowRadius)
                 .show(mTaskSurface);
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index 7f02072..acfb259 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -39,7 +39,25 @@
 }
 
 filegroup {
-    name: "WMShellFlickerTestsPip-src",
+    name: "WMShellFlickerTestsPip1-src",
+    srcs: [
+        "src/com/android/wm/shell/flicker/pip/A*.kt",
+        "src/com/android/wm/shell/flicker/pip/B*.kt",
+        "src/com/android/wm/shell/flicker/pip/C*.kt",
+        "src/com/android/wm/shell/flicker/pip/D*.kt",
+        "src/com/android/wm/shell/flicker/pip/S*.kt",
+    ],
+}
+
+filegroup {
+    name: "WMShellFlickerTestsPip2-src",
+    srcs: [
+        "src/com/android/wm/shell/flicker/pip/E*.kt",
+    ],
+}
+
+filegroup {
+    name: "WMShellFlickerTestsPip3-src",
     srcs: ["src/com/android/wm/shell/flicker/pip/*.kt"],
 }
 
@@ -176,7 +194,9 @@
     ],
     exclude_srcs: [
         ":WMShellFlickerTestsBubbles-src",
-        ":WMShellFlickerTestsPip-src",
+        ":WMShellFlickerTestsPip1-src",
+        ":WMShellFlickerTestsPip2-src",
+        ":WMShellFlickerTestsPip3-src",
         ":WMShellFlickerTestsPipCommon-src",
         ":WMShellFlickerTestsPipApps-src",
         ":WMShellFlickerTestsSplitScreenGroup1-src",
@@ -200,19 +220,49 @@
 }
 
 android_test {
-    name: "WMShellFlickerTestsPip",
+    name: "WMShellFlickerTestsPip1",
     defaults: ["WMShellFlickerTestsDefault"],
     additional_manifests: ["manifests/AndroidManifestPip.xml"],
     package_name: "com.android.wm.shell.flicker.pip",
     instrumentation_target_package: "com.android.wm.shell.flicker.pip",
     srcs: [
         ":WMShellFlickerTestsBase-src",
-        ":WMShellFlickerTestsPip-src",
+        ":WMShellFlickerTestsPip1-src",
         ":WMShellFlickerTestsPipCommon-src",
     ],
 }
 
 android_test {
+    name: "WMShellFlickerTestsPip2",
+    defaults: ["WMShellFlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestPip.xml"],
+    package_name: "com.android.wm.shell.flicker.pip",
+    instrumentation_target_package: "com.android.wm.shell.flicker.pip",
+    srcs: [
+        ":WMShellFlickerTestsBase-src",
+        ":WMShellFlickerTestsPip2-src",
+        ":WMShellFlickerTestsPipCommon-src",
+    ],
+}
+
+android_test {
+    name: "WMShellFlickerTestsPip3",
+    defaults: ["WMShellFlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestPip.xml"],
+    package_name: "com.android.wm.shell.flicker.pip",
+    instrumentation_target_package: "com.android.wm.shell.flicker.pip",
+    srcs: [
+        ":WMShellFlickerTestsBase-src",
+        ":WMShellFlickerTestsPip3-src",
+        ":WMShellFlickerTestsPipCommon-src",
+    ],
+    exclude_srcs: [
+        ":WMShellFlickerTestsPip1-src",
+        ":WMShellFlickerTestsPip2-src",
+    ],
+}
+
+android_test {
     name: "WMShellFlickerTestsPipApps",
     defaults: ["WMShellFlickerTestsDefault"],
     additional_manifests: ["manifests/AndroidManifestPip.xml"],
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index 19c8435..94e3959 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -22,6 +22,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.EnterPipTransition
 import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -55,7 +56,7 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) :
-    EnterPipViaAppUiButtonTest(flicker) {
+    EnterPipTransition(flicker) {
     override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
 
     override val defaultEnterPip: FlickerBuilder.() -> Unit = {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index c0c4498..add78b2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -6,6 +6,9 @@
 import android.os.IBinder
 import android.testing.AndroidTestingRunner
 import android.view.Display
+import android.view.Surface
+import android.view.Surface.ROTATION_270
+import android.view.Surface.ROTATION_90
 import android.view.SurfaceControl
 import android.window.WindowContainerToken
 import android.window.WindowContainerTransaction
@@ -24,6 +27,7 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
+import org.mockito.Mockito
 import org.mockito.Mockito.any
 import org.mockito.Mockito.argThat
 import org.mockito.Mockito.never
@@ -76,7 +80,15 @@
         whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
         whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
         whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
-            (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+            if (mockWindowDecoration.mTaskInfo.configuration.windowConfiguration
+                    .displayRotation == ROTATION_90 ||
+                mockWindowDecoration.mTaskInfo.configuration.windowConfiguration
+                    .displayRotation == ROTATION_270
+            ) {
+                (i.arguments.first() as Rect).set(STABLE_BOUNDS_LANDSCAPE)
+            } else {
+                (i.arguments.first() as Rect).set(STABLE_BOUNDS_PORTRAIT)
+            }
         }
         `when`(mockDisplayLayout.stableInsets()).thenReturn(STABLE_INSETS)
         `when`(mockTransactionFactory.get()).thenReturn(mockTransaction)
@@ -89,6 +101,7 @@
             defaultMinSize = DEFAULT_MIN
             displayId = DISPLAY_ID
             configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
+            configuration.windowConfiguration.displayRotation = ROTATION_90
         }
         mockWindowDecoration.mDisplay = mockDisplay
         whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
@@ -623,7 +636,7 @@
         )
 
         val newX = STARTING_BOUNDS.left.toFloat()
-        val newY = STABLE_BOUNDS.top.toFloat() - 5
+        val newY = STABLE_BOUNDS_LANDSCAPE.top.toFloat() - 5
         taskPositioner.onDragPositioningMove(
                 newX,
                 newY
@@ -641,11 +654,83 @@
                 token == taskBinder &&
                         (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
                         change.configuration.windowConfiguration.bounds.top ==
-                        STABLE_BOUNDS.top
+                        STABLE_BOUNDS_LANDSCAPE.top
             }
         })
     }
 
+    @Test
+    fun testDragResize_drag_updatesStableBoundsOnRotate() {
+        // Test landscape stable bounds
+        performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+            STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat() + 2000,
+            CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+        val rectAfterDrag = Rect(STARTING_BOUNDS)
+        rectAfterDrag.right += 2000
+        // First drag; we should fetch stable bounds.
+        verify(mockDisplayLayout, Mockito.times(1)).getStableBounds(any())
+        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+                        change.configuration.windowConfiguration.bounds == rectAfterDrag
+            }
+        })
+        // Drag back to starting bounds.
+        performDrag(
+            STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat(),
+            STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+            CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+
+        // Display did not rotate; we should use previous stable bounds
+        verify(mockDisplayLayout, Mockito.times(1)).getStableBounds(any())
+
+        // Rotate the screen to portrait
+        mockWindowDecoration.mTaskInfo.apply {
+            configuration.windowConfiguration.displayRotation = Surface.ROTATION_0
+        }
+        // Test portrait stable bounds
+        performDrag(
+            STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+            STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat() + 2000,
+            CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+        rectAfterDrag.right -= 2000
+        rectAfterDrag.bottom += 2000
+
+        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+                        change.configuration.windowConfiguration.bounds == rectAfterDrag
+            }
+        })
+        // Display has rotated; we expect a new stable bounds.
+        verify(mockDisplayLayout, Mockito.times(2)).getStableBounds(any())
+    }
+
+    private fun performDrag(
+        startX: Float,
+        startY: Float,
+        endX: Float,
+        endY: Float,
+        ctrlType: Int
+    ) {
+        taskPositioner.onDragPositioningStart(
+            ctrlType,
+            startX,
+            startY
+        )
+        taskPositioner.onDragPositioningMove(
+            endX,
+            endY
+        )
+
+        taskPositioner.onDragPositioningEnd(
+            endX,
+            endY
+        )
+    }
+
     companion object {
         private const val TASK_ID = 5
         private const val MIN_WIDTH = 10
@@ -664,11 +749,17 @@
                 DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT,
                 DISPLAY_BOUNDS.right,
                 DISPLAY_BOUNDS.bottom)
-        private val STABLE_BOUNDS = Rect(
+        private val STABLE_BOUNDS_LANDSCAPE = Rect(
                 DISPLAY_BOUNDS.left,
                 DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
                 DISPLAY_BOUNDS.right,
                 DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
         )
+        private val STABLE_BOUNDS_PORTRAIT = Rect(
+            DISPLAY_BOUNDS.top,
+            DISPLAY_BOUNDS.left + CAPTION_HEIGHT,
+            DISPLAY_BOUNDS.bottom,
+            DISPLAY_BOUNDS.right - NAVBAR_HEIGHT
+        )
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index 8913453..a70ebf1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -21,6 +21,9 @@
 import android.os.IBinder
 import android.testing.AndroidTestingRunner
 import android.view.Display
+import android.view.Surface.ROTATION_0
+import android.view.Surface.ROTATION_270
+import android.view.Surface.ROTATION_90
 import android.view.SurfaceControl
 import android.view.WindowManager.TRANSIT_CHANGE
 import android.window.WindowContainerToken
@@ -30,6 +33,7 @@
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.DisplayLayout
 import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM
 import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT
 import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP
 import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED
@@ -93,10 +97,17 @@
         whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
         whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
         whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
-            (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+            if (mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration
+                .displayRotation == ROTATION_90 ||
+                mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration
+                    .displayRotation == ROTATION_270
+            ) {
+                (i.arguments.first() as Rect).set(STABLE_BOUNDS_LANDSCAPE)
+            } else {
+                (i.arguments.first() as Rect).set(STABLE_BOUNDS_PORTRAIT)
+            }
         }
         `when`(mockTransactionFactory.get()).thenReturn(mockTransaction)
-
         mockDesktopWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
             taskId = TASK_ID
             token = taskToken
@@ -105,6 +116,7 @@
             defaultMinSize = DEFAULT_MIN
             displayId = DISPLAY_ID
             configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
+            configuration.windowConfiguration.displayRotation = ROTATION_90
         }
         mockDesktopWindowDecoration.mDisplay = mockDisplay
         whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
@@ -343,7 +355,7 @@
         )
 
         val newX = STARTING_BOUNDS.left.toFloat()
-        val newY = STABLE_BOUNDS.top.toFloat() - 5
+        val newY = STABLE_BOUNDS_LANDSCAPE.top.toFloat() - 5
         taskPositioner.onDragPositioningMove(
                 newX,
                 newY
@@ -361,11 +373,79 @@
                 token == taskBinder &&
                         (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
                         change.configuration.windowConfiguration.bounds.top ==
-                        STABLE_BOUNDS.top
+                        STABLE_BOUNDS_LANDSCAPE.top
             }
         })
     }
 
+    @Test
+    fun testDragResize_drag_updatesStableBoundsOnRotate() {
+        // Test landscape stable bounds
+        performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+            STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat() + 2000,
+            CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+        val rectAfterDrag = Rect(STARTING_BOUNDS)
+        rectAfterDrag.right += 2000
+        // First drag; we should fetch stable bounds.
+        verify(mockDisplayLayout, times(1)).getStableBounds(any())
+        verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+                        change.configuration.windowConfiguration.bounds == rectAfterDrag}},
+            eq(taskPositioner))
+        // Drag back to starting bounds.
+        performDrag(STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat(),
+            STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+            CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+
+        // Display did not rotate; we should use previous stable bounds
+        verify(mockDisplayLayout, times(1)).getStableBounds(any())
+
+        // Rotate the screen to portrait
+        mockDesktopWindowDecoration.mTaskInfo.apply {
+            configuration.windowConfiguration.displayRotation = ROTATION_0
+        }
+        // Test portrait stable bounds
+        performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+            STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat() + 2000,
+            CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+        rectAfterDrag.right -= 2000
+        rectAfterDrag.bottom += 2000
+
+        verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+                        change.configuration.windowConfiguration.bounds == rectAfterDrag}},
+            eq(taskPositioner))
+        // Display has rotated; we expect a new stable bounds.
+        verify(mockDisplayLayout, times(2)).getStableBounds(any())
+    }
+
+    private fun performDrag(
+        startX: Float,
+        startY: Float,
+        endX: Float,
+        endY: Float,
+        ctrlType: Int
+    ) {
+        taskPositioner.onDragPositioningStart(
+            ctrlType,
+            startX,
+            startY
+        )
+        taskPositioner.onDragPositioningMove(
+            endX,
+            endY
+        )
+
+        taskPositioner.onDragPositioningEnd(
+            endX,
+            endY
+        )
+    }
+
     companion object {
         private const val TASK_ID = 5
         private const val MIN_WIDTH = 10
@@ -378,11 +458,17 @@
         private const val DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT = 10
         private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
         private val STARTING_BOUNDS = Rect(100, 100, 200, 200)
-        private val STABLE_BOUNDS = Rect(
+        private val STABLE_BOUNDS_LANDSCAPE = Rect(
             DISPLAY_BOUNDS.left,
             DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
             DISPLAY_BOUNDS.right,
             DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
         )
+        private val STABLE_BOUNDS_PORTRAIT = Rect(
+            DISPLAY_BOUNDS.top,
+            DISPLAY_BOUNDS.left + CAPTION_HEIGHT,
+            DISPLAY_BOUNDS.bottom,
+            DISPLAY_BOUNDS.right - NAVBAR_HEIGHT
+        )
     }
 }
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 7f22693..d056248 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -785,7 +785,7 @@
       has_locale = true;
     }
 
-    // if we don't have a result yet
+      // if we don't have a result yet
     if (!final_result ||
         // or this config is better before the locale than the existing result
         result->config.isBetterThanBeforeLocale(final_result->config, desired_config) ||
@@ -863,9 +863,12 @@
 
       // We can skip calling ResTable_config::match() if the caller does not care for the
       // configuration to match or if we're using the list of types that have already had their
-      // configuration matched.
+      // configuration matched. The exception to this is when the user has multiple locales set
+      // because the filtered list will then have values from multiple locales and we will need to
+      // call match() to make sure the current entry matches the config we are currently checking.
       const ResTable_config& this_config = type_entry->config;
-      if (!(use_filtered || ignore_configuration || this_config.match(desired_config))) {
+      if (!((use_filtered && (configurations_.size() == 1))
+          || ignore_configuration || this_config.match(desired_config))) {
         continue;
       }
 
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index b00fc69..14602ef 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -139,6 +139,7 @@
     mRenderNodes.clear();
     mRenderThread.cacheManager().unregisterCanvasContext(this);
     mRenderThread.renderState().removeContextCallback(this);
+    mHintSessionWrapper->destroy();
 }
 
 void CanvasContext::addRenderNode(RenderNode* node, bool placeFront) {
diff --git a/libs/hwui/renderthread/HintSessionWrapper.cpp b/libs/hwui/renderthread/HintSessionWrapper.cpp
index 1c3399a..2362331 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.cpp
+++ b/libs/hwui/renderthread/HintSessionWrapper.cpp
@@ -158,7 +158,6 @@
 void HintSessionWrapper::sendLoadIncreaseHint() {
     if (!init()) return;
     mBinding->sendHint(mHintSession, static_cast<int32_t>(SessionHint::CPU_LOAD_UP));
-    mLastFrameNotification = systemTime();
 }
 
 bool HintSessionWrapper::alive() {
diff --git a/libs/hwui/tests/unit/HintSessionWrapperTests.cpp b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
index a14ae1c..10a740a1 100644
--- a/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
+++ b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
@@ -259,6 +259,31 @@
 
 TEST_F(HintSessionWrapperTests, delayedDeletionDoesNotKillReusedSession) {
     EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(0);
+    EXPECT_CALL(*sMockBinding, fakeReportActualWorkDuration(sessionPtr, 5_ms)).Times(1);
+
+    mWrapper->init();
+    waitForWrapperReady();
+    // Init a second time just to grab the wrapper from the promise
+    mWrapper->init();
+    EXPECT_EQ(mWrapper->alive(), true);
+
+    // First schedule the deletion
+    scheduleDelayedDestroyManaged();
+
+    // Then, report an actual duration
+    mWrapper->reportActualWorkDuration(5_ms);
+
+    // Then, run the delayed deletion after sending the update
+    allowDelayedDestructionToStart();
+    waitForDelayedDestructionToFinish();
+
+    // Ensure it didn't close within the timeframe of the test
+    Mock::VerifyAndClearExpectations(sMockBinding.get());
+    EXPECT_EQ(mWrapper->alive(), true);
+}
+
+TEST_F(HintSessionWrapperTests, loadUpDoesNotResetDeletionTimer) {
+    EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
     EXPECT_CALL(*sMockBinding,
                 fakeSendHint(sessionPtr, static_cast<int32_t>(SessionHint::CPU_LOAD_UP)))
             .Times(1);
@@ -272,16 +297,46 @@
     // First schedule the deletion
     scheduleDelayedDestroyManaged();
 
-    // Then, send a hint to update the timestamp
+    // Then, send a load_up hint
     mWrapper->sendLoadIncreaseHint();
 
     // Then, run the delayed deletion after sending the update
     allowDelayedDestructionToStart();
     waitForDelayedDestructionToFinish();
 
-    // Ensure it didn't close within the timeframe of the test
+    // Ensure it closed within the timeframe of the test
     Mock::VerifyAndClearExpectations(sMockBinding.get());
+    EXPECT_EQ(mWrapper->alive(), false);
+}
+
+TEST_F(HintSessionWrapperTests, manualSessionDestroyPlaysNiceWithDelayedDestruct) {
+    EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
+
+    mWrapper->init();
+    waitForWrapperReady();
+    // Init a second time just to grab the wrapper from the promise
+    mWrapper->init();
     EXPECT_EQ(mWrapper->alive(), true);
+
+    // First schedule the deletion
+    scheduleDelayedDestroyManaged();
+
+    // Then, kill the session
+    mWrapper->destroy();
+
+    // Verify it died
+    Mock::VerifyAndClearExpectations(sMockBinding.get());
+    EXPECT_EQ(mWrapper->alive(), false);
+
+    EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(0);
+
+    // Then, run the delayed deletion after manually killing the session
+    allowDelayedDestructionToStart();
+    waitForDelayedDestructionToFinish();
+
+    // Ensure it didn't close again and is still dead
+    Mock::VerifyAndClearExpectations(sMockBinding.get());
+    EXPECT_EQ(mWrapper->alive(), false);
 }
 
 }  // namespace android::uirenderer::renderthread
\ No newline at end of file
diff --git a/media/Android.bp b/media/Android.bp
index f69dd3c..3493408 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -23,6 +23,10 @@
     name: "soundtrigger_middleware-aidl",
     unstable: true,
     local_include_dir: "aidl",
+    defaults: [
+        "latest_android_media_audio_common_types_import_interface",
+        "latest_android_media_soundtrigger_types_import_interface",
+    ],
     backend: {
         java: {
             sdk_version: "module_current",
@@ -32,8 +36,6 @@
         "aidl/android/media/soundtrigger_middleware/*.aidl",
     ],
     imports: [
-        "android.media.audio.common.types-V2",
-        "android.media.soundtrigger.types-V1",
         "media_permission-aidl",
     ],
 }
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index 7eb0c76..4a5b4f2 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -74,8 +74,7 @@
 
     // Methods for MediaRouter2Manager
     List<RoutingSessionInfo> getRemoteSessions(IMediaRouter2Manager manager);
-    RoutingSessionInfo getSystemSessionInfoForPackage(
-            IMediaRouter2Manager manager, String packageName);
+    RoutingSessionInfo getSystemSessionInfoForPackage(String packageName);
     void registerManager(IMediaRouter2Manager manager, String packageName);
     void unregisterManager(IMediaRouter2Manager manager);
     void setRouteVolumeWithManager(IMediaRouter2Manager manager, int requestId,
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 91fa873..cccf6f1 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -18,6 +18,9 @@
 
 import static android.media.MediaRouter2Utils.toUniqueId;
 
+import static com.android.media.flags.Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER;
+
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -141,6 +144,8 @@
                 TYPE_WIRED_HEADPHONES,
                 TYPE_BLUETOOTH_A2DP,
                 TYPE_HDMI,
+                TYPE_HDMI_ARC,
+                TYPE_HDMI_EARC,
                 TYPE_USB_DEVICE,
                 TYPE_USB_ACCESSORY,
                 TYPE_DOCK,
@@ -206,6 +211,22 @@
     public static final int TYPE_HDMI = AudioDeviceInfo.TYPE_HDMI;
 
     /**
+     * Indicates the route is an Audio Return Channel of an HDMI connection.
+     *
+     * @see #getType
+     */
+    @FlaggedApi(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
+    public static final int TYPE_HDMI_ARC = AudioDeviceInfo.TYPE_HDMI_ARC;
+
+    /**
+     * Indicates the route is an Enhanced Audio Return Channel of an HDMI connection.
+     *
+     * @see #getType
+     */
+    @FlaggedApi(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
+    public static final int TYPE_HDMI_EARC = AudioDeviceInfo.TYPE_HDMI_EARC;
+
+    /**
      * Indicates the route is a USB audio device.
      *
      * @see #getType
@@ -907,6 +928,10 @@
                 return "BLUETOOTH_A2DP";
             case TYPE_HDMI:
                 return "HDMI";
+            case TYPE_HDMI_ARC:
+                return "HDMI_ARC";
+            case TYPE_HDMI_EARC:
+                return "HDMI_EARC";
             case TYPE_DOCK:
                 return "DOCK";
             case TYPE_USB_DEVICE:
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 2169090..76a00ac 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -310,8 +310,11 @@
                 IMediaRouterService.Stub.asInterface(
                         ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE));
 
+        mSystemController =
+                new SystemRoutingController(
+                        ProxyMediaRouter2Impl.getSystemSessionInfoImpl(
+                                mMediaRouterService, clientPackageName));
         mImpl = new ProxyMediaRouter2Impl(context, clientPackageName);
-        mSystemController = new SystemRoutingController(mImpl.getSystemSessionInfo());
     }
 
     /**
@@ -2057,15 +2060,7 @@
 
         @Override
         public RoutingSessionInfo getSystemSessionInfo() {
-            RoutingSessionInfo result;
-            try {
-                result =
-                        mMediaRouterService.getSystemSessionInfoForPackage(
-                                mClient, mClientPackageName);
-            } catch (RemoteException ex) {
-                throw ex.rethrowFromSystemServer();
-            }
-            return ensureClientPackageNameForSystemSession(result);
+            return getSystemSessionInfoImpl(mMediaRouterService, mClientPackageName);
         }
 
         /**
@@ -2430,6 +2425,23 @@
         }
 
         /**
+         * Retrieves the system session info for the given package.
+         *
+         * <p>The returned routing session is guaranteed to have a non-null {@link
+         * RoutingSessionInfo#getClientPackageName() client package name}.
+         *
+         * <p>Extracted into a static method to allow calling this from the constructor.
+         */
+        /* package */ static RoutingSessionInfo getSystemSessionInfoImpl(
+                @NonNull IMediaRouterService service, @NonNull String clientPackageName) {
+            try {
+                return service.getSystemSessionInfoForPackage(clientPackageName);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
+            }
+        }
+
+        /**
          * Sets the routing session's {@linkplain RoutingSessionInfo#getClientPackageName() client
          * package name} to {@link #mClientPackageName} if empty and returns the session.
          *
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 3abfc629..830708c 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -377,7 +377,7 @@
     @Nullable
     public RoutingSessionInfo getSystemRoutingSession(@Nullable String packageName) {
         try {
-            return mMediaRouterService.getSystemSessionInfoForPackage(mClient, packageName);
+            return mMediaRouterService.getSystemSessionInfoForPackage(packageName);
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
         }
diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl
index 31e65eb..d0e860c 100644
--- a/media/java/android/media/projection/IMediaProjectionManager.aidl
+++ b/media/java/android/media/projection/IMediaProjectionManager.aidl
@@ -176,4 +176,21 @@
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
             + ".permission.MANAGE_MEDIA_PROJECTION)")
     oneway void notifyPermissionRequestStateChange(int hostUid, int state, int sessionCreationSource);
+
+    /**
+     * Notifies system server that the permission request was initiated.
+     *
+     * <p>Only used for emitting atoms.
+     *
+     * @param hostUid               The uid of the process requesting consent to capture, may be an app or
+     *                              SystemUI.
+     * @param sessionCreationSource Only set if the state is MEDIA_PROJECTION_STATE_INITIATED.
+     *                              Indicates the entry point for requesting the permission. Must be
+     *                              a valid state defined
+     *                              in the SessionCreationSource enum.
+     */
+    @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION")
+    @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+            + ".permission.MANAGE_MEDIA_PROJECTION)")
+    oneway void notifyPermissionRequestInitiated(int hostUid, int sessionCreationSource);
 }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 01596d2..d62b490 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -40,6 +40,7 @@
 import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider
 import com.android.settingslib.spa.gallery.page.SettingsPagerPageProvider
 import com.android.settingslib.spa.gallery.page.SliderPageProvider
+import com.android.settingslib.spa.gallery.preference.ListPreferencePageProvider
 import com.android.settingslib.spa.gallery.preference.MainSwitchPreferencePageProvider
 import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider
 import com.android.settingslib.spa.gallery.preference.PreferencePageProvider
@@ -74,6 +75,7 @@
                 PreferencePageProvider,
                 SwitchPreferencePageProvider,
                 MainSwitchPreferencePageProvider,
+                ListPreferencePageProvider,
                 TwoTargetSwitchPreferencePageProvider,
                 ArgumentPageProvider,
                 SliderPageProvider,
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ListPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ListPreferencePageProvider.kt
new file mode 100644
index 0000000..43b6d0b
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ListPreferencePageProvider.kt
@@ -0,0 +1,120 @@
+/*
+ * 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.settingslib.spa.gallery.preference
+
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.ListPreference
+import com.android.settingslib.spa.widget.preference.ListPreferenceModel
+import com.android.settingslib.spa.widget.preference.ListPreferenceOption
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.flow
+
+private const val TITLE = "Sample ListPreference"
+
+object ListPreferencePageProvider : SettingsPageProvider {
+    override val name = "ListPreference"
+    private val owner = createSettingsPage()
+
+    override fun buildEntry(arguments: Bundle?) = listOf(
+        SettingsEntryBuilder.create("ListPreference", owner)
+            .setUiLayoutFn {
+                SampleListPreference()
+            }.build(),
+        SettingsEntryBuilder.create("ListPreference not changeable", owner)
+            .setUiLayoutFn {
+                SampleNotChangeableListPreference()
+            }.build(),
+    )
+
+    fun buildInjectEntry(): SettingsEntryBuilder {
+        return SettingsEntryBuilder.createInject(owner)
+            .setUiLayoutFn {
+                Preference(object : PreferenceModel {
+                    override val title = TITLE
+                    override val onClick = navigator(name)
+                })
+            }
+    }
+
+    override fun getTitle(arguments: Bundle?) = TITLE
+}
+
+@Composable
+private fun SampleListPreference() {
+    val selectedId = rememberSaveable { mutableIntStateOf(1) }
+    ListPreference(remember {
+        object : ListPreferenceModel {
+            override val title = "Preferred network type"
+            override val options = listOf(
+                ListPreferenceOption(id = 1, text = "5G (recommended)"),
+                ListPreferenceOption(id = 2, text = "LTE"),
+                ListPreferenceOption(id = 3, text = "3G"),
+            )
+            override val selectedId = selectedId
+            override val onIdSelected: (id: Int) -> Unit = { selectedId.intValue = it }
+        }
+    })
+}
+
+@Composable
+private fun SampleNotChangeableListPreference() {
+    val selectedId = rememberSaveable { mutableIntStateOf(1) }
+    val enableFlow = flow {
+        var enabled = true
+        while (true) {
+            delay(3.seconds)
+            enabled = !enabled
+            emit(enabled)
+        }
+    }
+    val enabled = enableFlow.collectAsStateWithLifecycle(initialValue = true)
+    ListPreference(remember {
+        object : ListPreferenceModel {
+            override val title = "Preferred network type"
+            override val enabled = enabled
+            override val options = listOf(
+                ListPreferenceOption(id = 1, text = "5G (recommended)"),
+                ListPreferenceOption(id = 2, text = "LTE"),
+                ListPreferenceOption(id = 3, text = "3G"),
+            )
+            override val selectedId = selectedId
+            override val onIdSelected: (id: Int) -> Unit = { selectedId.intValue = it }
+        }
+    })
+}
+
+@Preview
+@Composable
+private fun ListPreferencePagePreview() {
+    SettingsTheme {
+        ListPreferencePageProvider.Page(null)
+    }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
similarity index 95%
rename from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
index eddede7..ce9678b 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
@@ -36,6 +36,7 @@
             PreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             SwitchPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             MainSwitchPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            ListPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             TwoTargetSwitchPreferencePageProvider.buildInjectEntry()
                 .setLink(fromPage = owner).build(),
         )
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index 7962e60..4088ffd 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -40,8 +40,18 @@
     /** The size when app icon is displayed in App info page. */
     val appIconInfoSize = 48.dp
 
+    /** The vertical padding for buttons. */
+    val buttonPaddingVertical = 12.dp
+
     /** The [PaddingValues] for buttons. */
-    val buttonPadding = PaddingValues(horizontal = itemPaddingEnd, vertical = 12.dp)
+    val buttonPadding = PaddingValues(horizontal = itemPaddingEnd, vertical = buttonPaddingVertical)
+
+    /** The horizontal padding for dialog items. */
+    val dialogItemPaddingHorizontal = itemPaddingStart
+
+    /** The [PaddingValues] for dialog items. */
+    val dialogItemPadding =
+        PaddingValues(horizontal = dialogItemPaddingHorizontal, vertical = buttonPaddingVertical)
 
     /** The sizes info of illustration widget. */
     val illustrationMaxWidth = 412.dp
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt
index c8faef6..a9cd0e9 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt
@@ -16,10 +16,15 @@
 
 package com.android.settingslib.spa.framework.theme
 
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+
 object SettingsOpacity {
     const val Full = 1f
     const val Disabled = 0.38f
     const val Divider = 0.2f
     const val SurfaceTone = 0.14f
     const val Hint = 0.9f
+
+    fun Modifier.alphaForEnabled(enabled: Boolean) = alpha(if (enabled) Full else Disabled)
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
index c66e20a..8c862d4 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
@@ -22,5 +22,5 @@
 object SettingsShape {
     val CornerMedium = RoundedCornerShape(12.dp)
 
-    val CornerLarge = RoundedCornerShape(24.dp)
+    val CornerExtraLarge = RoundedCornerShape(28.dp)
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryHighlight.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryHighlight.kt
index 90c44b5..330755c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryHighlight.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryHighlight.kt
@@ -30,11 +30,11 @@
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
 import com.android.settingslib.spa.framework.common.LocalEntryDataProvider
-import com.android.settingslib.spa.framework.theme.SettingsTheme
 
 @Composable
-internal fun EntryHighlight(UiLayoutFn: @Composable () -> Unit) {
+internal fun EntryHighlight(content: @Composable () -> Unit) {
     val entryData = LocalEntryDataProvider.current
     val entryIsHighlighted = rememberSaveable { entryData.isHighlighted }
     var localHighlighted by rememberSaveable { mutableStateOf(false) }
@@ -45,15 +45,16 @@
     val backgroundColor by animateColorAsState(
         targetValue = when {
             localHighlighted -> MaterialTheme.colorScheme.surfaceVariant
-            else -> SettingsTheme.colorScheme.background
+            else -> Color.Transparent
         },
         animationSpec = repeatable(
             iterations = 3,
             animation = tween(durationMillis = 500),
             repeatMode = RepeatMode.Restart
-        )
+        ),
+        label = "BackgroundColorAnimation",
     )
     Box(modifier = Modifier.background(color = backgroundColor)) {
-        UiLayoutFn()
+        content()
     }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
index 1ad075c..979cf3b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
@@ -65,7 +65,7 @@
     Row(
         Modifier
             .padding(SettingsDimension.buttonPadding)
-            .clip(SettingsShape.CornerLarge)
+            .clip(SettingsShape.CornerExtraLarge)
             .height(IntrinsicSize.Min)
     ) {
         for ((index, actionButton) in actionButtons.withIndex()) {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsDialog.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsDialog.kt
new file mode 100644
index 0000000..8b172da
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsDialog.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.settingslib.spa.widget.dialog
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Card
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.window.Dialog
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsShape
+import com.android.settingslib.spa.widget.ui.SettingsTitle
+
+@Composable
+fun SettingsDialog(
+    title: String,
+    onDismissRequest: () -> Unit,
+    content: @Composable () -> Unit,
+) {
+    Dialog(onDismissRequest = onDismissRequest) {
+        Card(shape = SettingsShape.CornerExtraLarge) {
+            Column(modifier = Modifier.padding(vertical = SettingsDimension.itemPaddingAround)) {
+                Box(modifier = Modifier.padding(SettingsDimension.dialogItemPadding)) {
+                    SettingsTitle(title = title, useMediumWeight = true)
+                }
+                content()
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
index 6330ddf..4d42fba 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
@@ -29,13 +29,12 @@
 import androidx.compose.runtime.State
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.alpha
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import com.android.settingslib.spa.framework.compose.toState
 import com.android.settingslib.spa.framework.theme.SettingsDimension
-import com.android.settingslib.spa.framework.theme.SettingsOpacity
+import com.android.settingslib.spa.framework.theme.SettingsOpacity.alphaForEnabled
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.widget.ui.SettingsTitle
 
@@ -57,8 +56,7 @@
             .padding(end = paddingEnd),
         verticalAlignment = Alignment.CenterVertically,
     ) {
-        val alphaModifier =
-            Modifier.alpha(if (enabled.value) SettingsOpacity.Full else SettingsOpacity.Disabled)
+        val alphaModifier = Modifier.alphaForEnabled(enabled.value)
         BaseIcon(icon, alphaModifier, paddingStart)
         Titles(
             title = title,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ListPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ListPreference.kt
new file mode 100644
index 0000000..19779f6
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ListPreference.kt
@@ -0,0 +1,138 @@
+/*
+ * 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.settingslib.spa.widget.preference
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.selection.selectable
+import androidx.compose.foundation.selection.selectableGroup
+import androidx.compose.material3.RadioButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.IntState
+import androidx.compose.runtime.State
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.Role
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.widget.dialog.SettingsDialog
+import com.android.settingslib.spa.widget.ui.SettingsDialogItem
+
+data class ListPreferenceOption(
+    val id: Int,
+    val text: String,
+)
+
+/**
+ * The widget model for [ListPreference] widget.
+ */
+interface ListPreferenceModel {
+    /**
+     * The title of this [ListPreference].
+     */
+    val title: String
+
+    /**
+     * The icon of this [ListPreference].
+     *
+     * Default is `null` which means no icon.
+     */
+    val icon: (@Composable () -> Unit)?
+        get() = null
+
+    /**
+     * Indicates whether this [ListPreference] is enabled.
+     *
+     * Disabled [ListPreference] will be displayed in disabled style.
+     */
+    val enabled: State<Boolean>
+        get() = stateOf(true)
+
+    val options: List<ListPreferenceOption>
+
+    val selectedId: IntState
+
+    val onIdSelected: (id: Int) -> Unit
+}
+
+@Composable
+fun ListPreference(model: ListPreferenceModel) {
+    var dialogOpened by rememberSaveable { mutableStateOf(false) }
+    if (dialogOpened) {
+        SettingsDialog(
+            title = model.title,
+            onDismissRequest = { dialogOpened = false },
+        ) {
+            Column(modifier = Modifier.selectableGroup()) {
+                for (option in model.options) {
+                    Radio(option, model.selectedId, model.enabled) {
+                        dialogOpened = false
+                        model.onIdSelected(it)
+                    }
+                }
+            }
+        }
+    }
+    Preference(model = remember(model) {
+        object : PreferenceModel {
+            override val title = model.title
+            override val summary = derivedStateOf {
+                model.options.find { it.id == model.selectedId.intValue }?.text ?: ""
+            }
+            override val icon = model.icon
+            override val enabled = model.enabled
+            override val onClick = { dialogOpened = true }.takeIf { model.options.isNotEmpty() }
+        }
+    })
+}
+
+@Composable
+private fun Radio(
+    option: ListPreferenceOption,
+    selectedId: IntState,
+    enabledState: State<Boolean>,
+    onIdSelected: (id: Int) -> Unit,
+) {
+    val selected = option.id == selectedId.intValue
+    val enabled = enabledState.value
+    Row(
+        modifier = Modifier
+            .fillMaxWidth()
+            .selectable(
+                selected = selected,
+                enabled = enabled,
+                onClick = { onIdSelected(option.id) },
+                role = Role.RadioButton,
+            )
+            .padding(SettingsDimension.dialogItemPadding),
+        verticalAlignment = Alignment.CenterVertically,
+    ) {
+        RadioButton(selected = selected, onClick = null, enabled = enabled)
+        Spacer(modifier = Modifier.width(SettingsDimension.itemPaddingEnd))
+        SettingsDialogItem(text = option.text, enabled = enabled)
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
index 3e04b16..0c16c8b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
@@ -39,7 +39,7 @@
                 true -> MaterialTheme.colorScheme.primaryContainer
                 else -> MaterialTheme.colorScheme.secondaryContainer
             },
-            shape = SettingsShape.CornerLarge,
+            shape = SettingsShape.CornerExtraLarge,
         ) {
             InternalSwitchPreference(
                 title = model.title,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
index 6ef4590..5f320f7 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
@@ -18,19 +18,14 @@
 
 import androidx.appcompat.R
 import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.outlined.ArrowBack
 import androidx.compose.material.icons.outlined.Clear
 import androidx.compose.material.icons.outlined.FindInPage
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.composed
-import androidx.compose.ui.draw.scale
-import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.LayoutDirection
 import com.android.settingslib.spa.framework.compose.LocalNavController
-import androidx.compose.material.icons.automirrored.outlined.ArrowBack
 
 /** Action that navigates back to last page. */
 @Composable
@@ -55,7 +50,6 @@
         Icon(
             imageVector = Icons.AutoMirrored.Outlined.ArrowBack,
             contentDescription = contentDescription,
-            modifier = Modifier.autoMirrored(),
         )
     }
 }
@@ -81,10 +75,3 @@
         )
     }
 }
-
-private fun Modifier.autoMirrored() = composed {
-    when (LocalLayoutDirection.current) {
-        LayoutDirection.Rtl -> scale(scaleX = -1f, scaleY = 1f)
-        else -> this
-    }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
index 57319e7..7f1acff 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
@@ -30,6 +30,7 @@
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
 import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsOpacity.alphaForEnabled
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.framework.theme.toMediumWeight
 
@@ -48,6 +49,17 @@
 }
 
 @Composable
+fun SettingsDialogItem(text: String, enabled: Boolean = true) {
+    Text(
+        text = text,
+        modifier = Modifier.alphaForEnabled(enabled),
+        color = MaterialTheme.colorScheme.onSurface,
+        style = MaterialTheme.typography.bodyLarge,
+        overflow = TextOverflow.Ellipsis,
+    )
+}
+
+@Composable
 fun SettingsBody(
     body: String,
     maxLines: Int = Int.MAX_VALUE,
@@ -82,6 +94,9 @@
 private fun BasePreferencePreview() {
     SettingsTheme {
         Column(Modifier.width(100.dp)) {
+            SettingsTitle(
+                title = "Title",
+            )
             SettingsBody(
                 body = "Long long long long long long text",
             )
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/dialog/SettingsDialogTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/dialog/SettingsDialogTest.kt
new file mode 100644
index 0000000..c7582b2
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/dialog/SettingsDialogTest.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.settingslib.spa.widget.dialog
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.onDialogText
+import com.android.settingslib.spa.widget.ui.SettingsDialogItem
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsDialogTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun title_displayed() {
+        composeTestRule.setContent {
+            SettingsDialog(title = TITLE, onDismissRequest = {}) {}
+        }
+
+        composeTestRule.onDialogText(TITLE).assertIsDisplayed()
+    }
+
+    @Test
+    fun text_displayed() {
+        composeTestRule.setContent {
+            SettingsDialog(title = "", onDismissRequest = {}) {
+                SettingsDialogItem(text = TEXT)
+            }
+        }
+
+        composeTestRule.onDialogText(TEXT).assertIsDisplayed()
+    }
+
+    private companion object {
+        const val TITLE = "Title"
+        const val TEXT = "Text"
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ListPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ListPreferenceTest.kt
new file mode 100644
index 0000000..997a023
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ListPreferenceTest.kt
@@ -0,0 +1,177 @@
+/*
+ * 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.settingslib.spa.widget.preference
+
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.testutils.onDialogText
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ListPreferenceTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun title_displayed() {
+        composeTestRule.setContent {
+            ListPreference(remember {
+                object : ListPreferenceModel {
+                    override val title = TITLE
+                    override val options = emptyList<ListPreferenceOption>()
+                    override val selectedId = mutableIntStateOf(0)
+                    override val onIdSelected: (Int) -> Unit = {}
+                }
+            })
+        }
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+    }
+
+    @Test
+    fun summary_showSelectedText() {
+        composeTestRule.setContent {
+            ListPreference(remember {
+                object : ListPreferenceModel {
+                    override val title = TITLE
+                    override val options = listOf(ListPreferenceOption(id = 1, text = "A"))
+                    override val selectedId = mutableIntStateOf(1)
+                    override val onIdSelected: (Int) -> Unit = {}
+                }
+            })
+        }
+
+        composeTestRule.onNodeWithText("A").assertIsDisplayed()
+    }
+
+    @Test
+    fun click_optionsIsEmpty_notShowDialog() {
+        composeTestRule.setContent {
+            ListPreference(remember {
+                object : ListPreferenceModel {
+                    override val title = TITLE
+                    override val options = emptyList<ListPreferenceOption>()
+                    override val selectedId = mutableIntStateOf(0)
+                    override val onIdSelected: (Int) -> Unit = {}
+                }
+            })
+        }
+
+        composeTestRule.onNodeWithText(TITLE).performClick()
+
+        composeTestRule.onDialogText(TITLE).assertDoesNotExist()
+    }
+
+    @Test
+    fun click_notEnabled_notShowDialog() {
+        composeTestRule.setContent {
+            ListPreference(remember {
+                object : ListPreferenceModel {
+                    override val title = TITLE
+                    override val enabled = stateOf(false)
+                    override val options = listOf(ListPreferenceOption(id = 1, text = "A"))
+                    override val selectedId = mutableIntStateOf(1)
+                    override val onIdSelected: (Int) -> Unit = {}
+                }
+            })
+        }
+
+        composeTestRule.onNodeWithText(TITLE).performClick()
+
+        composeTestRule.onDialogText(TITLE).assertDoesNotExist()
+    }
+
+    @Test
+    fun click_optionsNotEmpty_showDialog() {
+        composeTestRule.setContent {
+            ListPreference(remember {
+                object : ListPreferenceModel {
+                    override val title = TITLE
+                    override val options = listOf(ListPreferenceOption(id = 1, text = "A"))
+                    override val selectedId = mutableIntStateOf(1)
+                    override val onIdSelected: (Int) -> Unit = {}
+                }
+            })
+        }
+
+        composeTestRule.onNodeWithText(TITLE).performClick()
+
+        composeTestRule.onDialogText(TITLE).assertIsDisplayed()
+    }
+
+    @Test
+    fun select() {
+        val selectedId = mutableIntStateOf(1)
+        composeTestRule.setContent {
+            ListPreference(remember {
+                object : ListPreferenceModel {
+                    override val title = TITLE
+                    override val options = listOf(
+                        ListPreferenceOption(id = 1, text = "A"),
+                        ListPreferenceOption(id = 2, text = "B"),
+                    )
+                    override val selectedId = selectedId
+                    override val onIdSelected = { id: Int -> selectedId.intValue = id }
+                }
+            })
+        }
+
+        composeTestRule.onNodeWithText(TITLE).performClick()
+        composeTestRule.onDialogText("B").performClick()
+
+        composeTestRule.onNodeWithText("B").assertIsDisplayed()
+    }
+
+    @Test
+    fun select_dialogOpenThenDisable_itemAlsoDisabled() {
+        val selectedId = mutableIntStateOf(1)
+        val enabledState = mutableStateOf(true)
+        composeTestRule.setContent {
+            ListPreference(remember {
+                object : ListPreferenceModel {
+                    override val title = TITLE
+                    override val enabled = enabledState
+                    override val options = listOf(
+                        ListPreferenceOption(id = 1, text = "A"),
+                        ListPreferenceOption(id = 2, text = "B"),
+                    )
+                    override val selectedId = selectedId
+                    override val onIdSelected = { id: Int -> selectedId.intValue = id }
+                }
+            })
+        }
+
+        composeTestRule.onNodeWithText(TITLE).performClick()
+        enabledState.value = false
+
+        composeTestRule.onDialogText("B").assertIsDisplayed().assertIsNotEnabled()
+    }
+
+    private companion object {
+        const val TITLE = "Title"
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
index e38e041..2a28417 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
@@ -39,39 +39,50 @@
     @DrawableRes private static final int DEFAULT_ICON = R.drawable.ic_smartphone;
 
     public DeviceIconUtil() {
-        List<Device> deviceList = Arrays.asList(
-                new Device(
-                    AudioDeviceInfo.TYPE_USB_DEVICE,
-                    MediaRoute2Info.TYPE_USB_DEVICE,
-                    R.drawable.ic_headphone),
-                new Device(
-                    AudioDeviceInfo.TYPE_USB_HEADSET,
-                    MediaRoute2Info.TYPE_USB_HEADSET,
-                    R.drawable.ic_headphone),
-                new Device(
-                    AudioDeviceInfo.TYPE_USB_ACCESSORY,
-                    MediaRoute2Info.TYPE_USB_ACCESSORY,
-                    R.drawable.ic_headphone),
-                new Device(
-                    AudioDeviceInfo.TYPE_DOCK,
-                    MediaRoute2Info.TYPE_DOCK,
-                    R.drawable.ic_dock_device),
-                new Device(
-                    AudioDeviceInfo.TYPE_HDMI,
-                    MediaRoute2Info.TYPE_HDMI,
-                    R.drawable.ic_headphone),
-                new Device(
-                    AudioDeviceInfo.TYPE_WIRED_HEADSET,
-                    MediaRoute2Info.TYPE_WIRED_HEADSET,
-                    R.drawable.ic_headphone),
-                new Device(
-                    AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
-                    MediaRoute2Info.TYPE_WIRED_HEADPHONES,
-                    R.drawable.ic_headphone),
-                new Device(
-                    AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
-                    MediaRoute2Info.TYPE_BUILTIN_SPEAKER,
-                    R.drawable.ic_smartphone));
+        List<Device> deviceList =
+                Arrays.asList(
+                        new Device(
+                                AudioDeviceInfo.TYPE_USB_DEVICE,
+                                MediaRoute2Info.TYPE_USB_DEVICE,
+                                R.drawable.ic_headphone),
+                        new Device(
+                                AudioDeviceInfo.TYPE_USB_HEADSET,
+                                MediaRoute2Info.TYPE_USB_HEADSET,
+                                R.drawable.ic_headphone),
+                        new Device(
+                                AudioDeviceInfo.TYPE_USB_ACCESSORY,
+                                MediaRoute2Info.TYPE_USB_ACCESSORY,
+                                R.drawable.ic_headphone),
+                        new Device(
+                                AudioDeviceInfo.TYPE_DOCK,
+                                MediaRoute2Info.TYPE_DOCK,
+                                R.drawable.ic_dock_device),
+                        new Device(
+                                AudioDeviceInfo.TYPE_HDMI,
+                                MediaRoute2Info.TYPE_HDMI,
+                                R.drawable.ic_headphone),
+                        // TODO: b/306359110 - Put proper iconography for HDMI_ARC type.
+                        new Device(
+                                AudioDeviceInfo.TYPE_HDMI_ARC,
+                                MediaRoute2Info.TYPE_HDMI_ARC,
+                                R.drawable.ic_headphone),
+                        // TODO: b/306359110 - Put proper iconography for HDMI_EARC type.
+                        new Device(
+                                AudioDeviceInfo.TYPE_HDMI_EARC,
+                                MediaRoute2Info.TYPE_HDMI_EARC,
+                                R.drawable.ic_headphone),
+                        new Device(
+                                AudioDeviceInfo.TYPE_WIRED_HEADSET,
+                                MediaRoute2Info.TYPE_WIRED_HEADSET,
+                                R.drawable.ic_headphone),
+                        new Device(
+                                AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
+                                MediaRoute2Info.TYPE_WIRED_HEADPHONES,
+                                R.drawable.ic_headphone),
+                        new Device(
+                                AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
+                                MediaRoute2Info.TYPE_BUILTIN_SPEAKER,
+                                R.drawable.ic_smartphone));
         for (int i = 0; i < deviceList.size(); i++) {
             Device device = deviceList.get(i);
             mAudioDeviceTypeToIconMap.put(device.mAudioDeviceType, device);
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index bf63f5d..5dacba5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -21,6 +21,8 @@
 import static android.media.MediaRoute2Info.TYPE_DOCK;
 import static android.media.MediaRoute2Info.TYPE_GROUP;
 import static android.media.MediaRoute2Info.TYPE_HDMI;
+import static android.media.MediaRoute2Info.TYPE_HDMI_ARC;
+import static android.media.MediaRoute2Info.TYPE_HDMI_EARC;
 import static android.media.MediaRoute2Info.TYPE_HEARING_AID;
 import static android.media.MediaRoute2Info.TYPE_REMOTE_AUDIO_VIDEO_RECEIVER;
 import static android.media.MediaRoute2Info.TYPE_REMOTE_CAR;
@@ -635,6 +637,8 @@
             case TYPE_USB_ACCESSORY:
             case TYPE_DOCK:
             case TYPE_HDMI:
+            case TYPE_HDMI_ARC:
+            case TYPE_HDMI_EARC:
             case TYPE_WIRED_HEADSET:
             case TYPE_WIRED_HEADPHONES:
                 mediaDevice =
@@ -668,11 +672,12 @@
                                 route,
                                 mPackageName,
                                 mPreferenceItemMap.get(route.getId()));
+                break;
             default:
                 Log.w(TAG, "addMediaDevice() unknown device type : " + deviceType);
                 break;
-
         }
+
         if (mediaDevice != null && !TextUtils.isEmpty(mPackageName)
                 && getRoutingSessionInfo().getSelectedRoutes().contains(route.getId())) {
             mediaDevice.setState(STATE_SELECTED);
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index 147412d..8085c99 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -21,6 +21,8 @@
 import static android.media.MediaRoute2Info.TYPE_DOCK;
 import static android.media.MediaRoute2Info.TYPE_GROUP;
 import static android.media.MediaRoute2Info.TYPE_HDMI;
+import static android.media.MediaRoute2Info.TYPE_HDMI_ARC;
+import static android.media.MediaRoute2Info.TYPE_HDMI_EARC;
 import static android.media.MediaRoute2Info.TYPE_HEARING_AID;
 import static android.media.MediaRoute2Info.TYPE_REMOTE_AUDIO_VIDEO_RECEIVER;
 import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
@@ -140,7 +142,6 @@
             mType = MediaDeviceType.TYPE_BLUETOOTH_DEVICE;
             return;
         }
-
         switch (info.getType()) {
             case TYPE_GROUP:
                 mType = MediaDeviceType.TYPE_CAST_GROUP_DEVICE;
@@ -157,6 +158,8 @@
             case TYPE_USB_ACCESSORY:
             case TYPE_DOCK:
             case TYPE_HDMI:
+            case TYPE_HDMI_ARC:
+            case TYPE_HDMI_EARC:
                 mType = MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE;
                 break;
             case TYPE_HEARING_AID:
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index a63bbdf..c44f66e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -18,6 +18,8 @@
 import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
 import static android.media.MediaRoute2Info.TYPE_DOCK;
 import static android.media.MediaRoute2Info.TYPE_HDMI;
+import static android.media.MediaRoute2Info.TYPE_HDMI_ARC;
+import static android.media.MediaRoute2Info.TYPE_HDMI_EARC;
 import static android.media.MediaRoute2Info.TYPE_USB_ACCESSORY;
 import static android.media.MediaRoute2Info.TYPE_USB_DEVICE;
 import static android.media.MediaRoute2Info.TYPE_USB_HEADSET;
@@ -71,6 +73,8 @@
                 name = context.getString(R.string.media_transfer_this_device_name);
                 break;
             case TYPE_HDMI:
+            case TYPE_HDMI_ARC:
+            case TYPE_HDMI_EARC:
                 name = context.getString(R.string.media_transfer_external_device_name);
                 break;
             default:
@@ -144,6 +148,8 @@
             case TYPE_USB_ACCESSORY:
             case TYPE_DOCK:
             case TYPE_HDMI:
+            case TYPE_HDMI_ARC:
+            case TYPE_HDMI_EARC:
                 id = USB_HEADSET_ID;
                 break;
             case TYPE_BUILTIN_SPEAKER:
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index fe39c4f..59c3cd3 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -84,6 +84,7 @@
         Settings.System.RING_VIBRATION_INTENSITY,
         Settings.System.HAPTIC_FEEDBACK_INTENSITY,
         Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY,
+        Settings.System.KEYBOARD_VIBRATION_ENABLED,
         Settings.System.HAPTIC_FEEDBACK_ENABLED,
         Settings.System.DISPLAY_COLOR_MODE_VENDOR_HINT, // must precede DISPLAY_COLOR_MODE
         Settings.System.DISPLAY_COLOR_MODE,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index eba74ab..572303a 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -138,6 +138,7 @@
         VALIDATORS.put(System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
         VALIDATORS.put(System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
         VALIDATORS.put(System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
+        VALIDATORS.put(System.KEYBOARD_VIBRATION_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.HAPTIC_FEEDBACK_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.RINGTONE, URI_VALIDATOR);
         VALIDATORS.put(System.NOTIFICATION_SOUND, URI_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 46cd725..4e2fad0 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3243,7 +3243,7 @@
             }
 
             if (success && criticalSettings != null && criticalSettings.contains(name)) {
-                settingsState.persistSyncLocked();
+                settingsState.persistSettingsLocked();
             }
 
             if (forceNotify || success) {
@@ -3294,7 +3294,7 @@
             }
 
             if (success && criticalSettings != null && criticalSettings.contains(name)) {
-                settingsState.persistSyncLocked();
+                settingsState.persistSettingsLocked();
             }
 
             if (forceNotify || success) {
@@ -3319,7 +3319,7 @@
             }
 
             if (success && criticalSettings != null && criticalSettings.contains(name)) {
-                settingsState.persistSyncLocked();
+                settingsState.persistSettingsLocked();
             }
 
             if (forceNotify || success) {
@@ -3385,7 +3385,7 @@
                             }
                         }
                         if (someSettingChanged) {
-                            settingsState.persistSyncLocked();
+                            settingsState.persistSettingsLocked();
                             success = true;
                         }
                     }
@@ -3407,7 +3407,7 @@
                             }
                         }
                         if (someSettingChanged) {
-                            settingsState.persistSyncLocked();
+                            settingsState.persistSettingsLocked();
                             success = true;
                         }
                     }
@@ -3435,7 +3435,7 @@
                             }
                         }
                         if (someSettingChanged) {
-                            settingsState.persistSyncLocked();
+                            settingsState.persistSettingsLocked();
                             success = true;
                         }
                     }
@@ -3460,7 +3460,7 @@
                             logSettingChanged(userId, name, type, CHANGE_TYPE_DELETE);
                         }
                         if (someSettingChanged) {
-                            settingsState.persistSyncLocked();
+                            settingsState.persistSettingsLocked();
                             success = true;
                         }
                     }
@@ -3559,7 +3559,7 @@
             ensureSettingsStateLocked(systemKey);
             SettingsState systemSettings = mSettingsStates.get(systemKey);
             migrateLegacySettingsLocked(systemSettings, database, TABLE_SYSTEM);
-            systemSettings.persistSyncLocked();
+            systemSettings.persistSettingsLocked();
 
             // Move over the secure settings.
             // Do this after System settings, since this is the first thing we check when deciding
@@ -3569,7 +3569,7 @@
             SettingsState secureSettings = mSettingsStates.get(secureKey);
             migrateLegacySettingsLocked(secureSettings, database, TABLE_SECURE);
             ensureSecureSettingAndroidIdSetLocked(secureSettings);
-            secureSettings.persistSyncLocked();
+            secureSettings.persistSettingsLocked();
 
             // Move over the global settings if owner.
             // Do this last, since this is the first thing we check when deciding
@@ -3585,7 +3585,7 @@
                             mSettingsCreationBuildId, null, true,
                             SettingsState.SYSTEM_PACKAGE_NAME);
                 }
-                globalSettings.persistSyncLocked();
+                globalSettings.persistSettingsLocked();
             }
 
             // Drop the database as now all is moved and persisted.
@@ -4404,16 +4404,16 @@
                     if (userId == UserHandle.USER_SYSTEM) {
                         SettingsState globalSettings = getGlobalSettingsLocked();
                         ensureLegacyDefaultValueAndSystemSetUpdatedLocked(globalSettings, userId);
-                        globalSettings.persistSyncLocked();
+                        globalSettings.persistSettingsLocked();
                     }
 
                     SettingsState secureSettings = getSecureSettingsLocked(mUserId);
                     ensureLegacyDefaultValueAndSystemSetUpdatedLocked(secureSettings, userId);
-                    secureSettings.persistSyncLocked();
+                    secureSettings.persistSettingsLocked();
 
                     SettingsState systemSettings = getSystemSettingsLocked(mUserId);
                     ensureLegacyDefaultValueAndSystemSetUpdatedLocked(systemSettings, userId);
-                    systemSettings.persistSyncLocked();
+                    systemSettings.persistSettingsLocked();
 
                     currentVersion = 146;
                 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index e9533e5..7cec99d 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -72,6 +72,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.CountDownLatch;
 
 /**
  * This class contains the state for one type of settings. It is responsible
@@ -589,9 +590,10 @@
     }
 
     // The settings provider must hold its lock when calling here.
-    public void persistSyncLocked() {
+    public void persistSettingsLocked() {
         mHandler.removeMessages(MyHandler.MSG_PERSIST_SETTINGS);
-        doWriteState();
+        // schedule a write operation right away
+        mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS).sendToTarget();
     }
 
     // The settings provider must hold its lock when calling here.
@@ -1725,4 +1727,20 @@
             return mPackageToMemoryUsage.getOrDefault(packageName, 0);
         }
     }
+
+    /**
+     * Allow tests to wait for the handler to finish handling all the remaining messages
+     */
+    @VisibleForTesting
+    public void waitForHandler() {
+        final CountDownLatch latch = new CountDownLatch(1);
+        synchronized (mLock) {
+            mHandler.post(latch::countDown);
+        }
+        try {
+            latch.await();
+        } catch (InterruptedException e) {
+            // ignored
+        }
+    }
 }
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index df4d2a1..02a7bc1 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -76,6 +76,14 @@
         mSettingsFile.delete();
     }
 
+    @Override
+    protected void tearDown() throws Exception {
+        if (mSettingsFile != null) {
+            mSettingsFile.delete();
+        }
+        super.tearDown();
+    }
+
     public void testIsBinary() {
         assertFalse(SettingsState.isBinary(" abc 日本語"));
 
@@ -149,11 +157,10 @@
      * Make sure settings can be written to a file and also can be read.
      */
     public void testReadWrite() {
-        final File file = new File(getContext().getCacheDir(), "setting.xml");
-        file.delete();
         final Object lock = new Object();
 
-        final SettingsState ssWriter = new SettingsState(getContext(), lock, file, 1,
+        assertFalse(mSettingsFile.exists());
+        final SettingsState ssWriter = new SettingsState(getContext(), lock, mSettingsFile, 1,
                 SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
         ssWriter.setVersionLocked(SettingsState.SETTINGS_VERSION_NEW_ENCODING);
 
@@ -162,11 +169,13 @@
         ssWriter.insertSettingLocked("k3", null, null, false, "p2");
         ssWriter.insertSettingLocked("k4", CRAZY_STRING, null, false, "p3");
         synchronized (lock) {
-            ssWriter.persistSyncLocked();
+            ssWriter.persistSettingsLocked();
         }
-
-        final SettingsState ssReader = new SettingsState(getContext(), lock, file, 1,
+        ssWriter.waitForHandler();
+        assertTrue(mSettingsFile.exists());
+        final SettingsState ssReader = new SettingsState(getContext(), lock, mSettingsFile, 1,
                 SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+
         synchronized (lock) {
             assertEquals("\u0000", ssReader.getSettingLocked("k1").getValue());
             assertEquals("abc", ssReader.getSettingLocked("k2").getValue());
@@ -179,10 +188,8 @@
      * In version 120, value "null" meant {code NULL}.
      */
     public void testUpgrade() throws Exception {
-        final File file = new File(getContext().getCacheDir(), "setting.xml");
-        file.delete();
         final Object lock = new Object();
-        final PrintStream os = new PrintStream(new FileOutputStream(file));
+        final PrintStream os = new PrintStream(new FileOutputStream(mSettingsFile));
         os.print(
                 "<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>" +
                         "<settings version=\"120\">" +
@@ -192,7 +199,7 @@
                         "</settings>");
         os.close();
 
-        final SettingsState ss = new SettingsState(getContext(), lock, file, 1,
+        final SettingsState ss = new SettingsState(getContext(), lock, mSettingsFile, 1,
                 SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
         synchronized (lock) {
             SettingsState.Setting s;
@@ -213,7 +220,8 @@
     public void testInitializeSetting_preserveFlagNotSet() {
         SettingsState settingsWriter = getSettingStateObject();
         settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE);
-        settingsWriter.persistSyncLocked();
+        settingsWriter.persistSettingsLocked();
+        settingsWriter.waitForHandler();
 
         SettingsState settingsReader = getSettingStateObject();
         assertFalse(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
@@ -223,7 +231,8 @@
         SettingsState settingsWriter = getSettingStateObject();
         settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE);
         settingsWriter.insertSettingLocked(SETTING_NAME, "2", null, false, TEST_PACKAGE);
-        settingsWriter.persistSyncLocked();
+        settingsWriter.persistSettingsLocked();
+        settingsWriter.waitForHandler();
 
         SettingsState settingsReader = getSettingStateObject();
         assertTrue(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
@@ -234,7 +243,8 @@
         settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE);
         settingsWriter.insertSettingLocked(SETTING_NAME, "2", null, false, false, TEST_PACKAGE,
                 /* overrideableByRestore */ true);
-        settingsWriter.persistSyncLocked();
+        settingsWriter.persistSettingsLocked();
+        settingsWriter.waitForHandler();
 
         SettingsState settingsReader = getSettingStateObject();
         assertFalse(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
@@ -250,7 +260,8 @@
         // already been set to true.
         settingsWriter.insertSettingLocked(SETTING_NAME, "2", null, false, false, TEST_PACKAGE,
                 /* overrideableByRestore */ true);
-        settingsWriter.persistSyncLocked();
+        settingsWriter.persistSettingsLocked();
+        settingsWriter.waitForHandler();
 
         SettingsState settingsReader = getSettingStateObject();
         assertTrue(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
@@ -481,8 +492,11 @@
             settingsState.insertSettingLocked(
                     FLAG_NAME_1_STAGED, VALUE1, null, false, TEST_PACKAGE);
             settingsState.insertSettingLocked(FLAG_NAME_2, VALUE2, null, false, TEST_PACKAGE);
-            settingsState.persistSyncLocked();
+            settingsState.persistSettingsLocked();
+        }
+        settingsState.waitForHandler();
 
+        synchronized (lock) {
             assertEquals(VALUE1, settingsState.getSettingLocked(FLAG_NAME_1_STAGED).getValue());
             assertEquals(VALUE2, settingsState.getSettingLocked(FLAG_NAME_2).getValue());
         }
@@ -522,7 +536,10 @@
         synchronized (lock) {
             settingsState.insertSettingLocked(INVALID_STAGED_FLAG_1,
                     VALUE2, null, false, TEST_PACKAGE);
-            settingsState.persistSyncLocked();
+            settingsState.persistSettingsLocked();
+        }
+        settingsState.waitForHandler();
+        synchronized (lock) {
             assertEquals(VALUE2, settingsState.getSettingLocked(INVALID_STAGED_FLAG_1).getValue());
         }
 
diff --git a/packages/SoundPicker/Android.bp b/packages/SoundPicker/Android.bp
index 235e672..2c89d6d 100644
--- a/packages/SoundPicker/Android.bp
+++ b/packages/SoundPicker/Android.bp
@@ -7,40 +7,22 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
-android_library {
-    name: "SoundPickerLib",
-    srcs: [
-        "src/**/*.java",
-    ],
-    resource_dirs: [
-        "res",
-    ],
-    static_libs: [
-        "androidx.appcompat_appcompat",
-        "hilt_android",
-        "guava",
-        "androidx.recyclerview_recyclerview",
-        "androidx-constraintlayout_constraintlayout",
-        "androidx.viewpager2_viewpager2",
-        "com.google.android.material_material",
-    ],
-}
-
 android_app {
     name: "SoundPicker",
     defaults: ["platform_app_defaults"],
     manifest: "AndroidManifest.xml",
-    static_libs: ["SoundPickerLib"],
+
+    static_libs: [
+        "androidx.appcompat_appcompat",
+    ],
+    resource_dirs: [
+        "res",
+    ],
+    srcs: [
+        "src/**/*.java",
+    ],
+
     platform_apis: true,
     certificate: "media",
     privileged: true,
-
-    optimize: {
-        enabled: true,
-        optimize: true,
-        shrink: true,
-        shrink_resources: true,
-        obfuscate: false,
-        proguard_compatibility: false,
-    },
 }
diff --git a/packages/SoundPicker/AndroidManifest.xml b/packages/SoundPicker/AndroidManifest.xml
index 934b003..44295a5 100644
--- a/packages/SoundPicker/AndroidManifest.xml
+++ b/packages/SoundPicker/AndroidManifest.xml
@@ -1,6 +1,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.soundpicker"
-        android:sharedUserId="android.media">
+          package="com.android.soundpicker"
+          android:sharedUserId="android.media">
 
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
@@ -9,16 +9,12 @@
     <uses-permission android:name="android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY" />
     <uses-permission android:name="android.permission.WRITE_SETTINGS" />
 
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
-
     <application
-            android:name=".RingtonePickerApplication"
-            android:allowBackup="false"
-            android:label="@string/app_label"
-            android:theme="@style/Theme.AppCompat"
-            android:supportsRtl="true">
+        android:allowBackup="false"
+        android:label="@string/app_label"
+        android:supportsRtl="true">
         <receiver android:name="RingtoneReceiver"
-                android:exported="true">
+                  android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY"/>
             </intent-filter>
@@ -27,17 +23,14 @@
         <service android:name="RingtoneOverlayService" />
 
         <activity android:name="RingtonePickerActivity"
-                android:theme="@style/Theme.AppCompat.Dialog"
-                android:enabled="@*android:bool/config_defaultRingtonePickerEnabled"
-                android:excludeFromRecents="true"
-                android:exported="true">
+                  android:theme="@style/PickerDialogTheme"
+                  android:enabled="@*android:bool/config_defaultRingtonePickerEnabled"
+                  android:excludeFromRecents="true"
+                  android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.RINGTONE_PICKER" />
                 <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.RINGTONE_PICKER_SOUND" />
-                <category android:name="android.intent.category.RINGTONE_PICKER_VIBRATION" />
-                <category android:name="android.intent.category.RINGTONE_PICKER_RINGTONE" />
             </intent-filter>
         </activity>
     </application>
-</manifest>
+</manifest>
\ No newline at end of file
diff --git a/packages/SoundPicker/res/layout/add_new_sound_item.xml b/packages/SoundPicker/res/layout/add_new_sound_item.xml
index 024b97e..57b70d7 100644
--- a/packages/SoundPicker/res/layout/add_new_sound_item.xml
+++ b/packages/SoundPicker/res/layout/add_new_sound_item.xml
@@ -19,9 +19,7 @@
               android:layout_width="fill_parent"
               android:layout_height="wrap_content"
               android:gravity="center_vertical"
-              android:background="?android:attr/selectableItemBackground"
-              android:focusable="true"
-              android:clickable="true">
+              android:background="?android:attr/selectableItemBackground">
 
     <ImageView
         android:layout_width="24dp"
@@ -31,19 +29,19 @@
         android:scaleType="centerCrop"
         android:layout_marginRight="24dp"
         android:layout_marginLeft="24dp"
-        android:src="@drawable/ic_add"/>
+        android:src="@drawable/ic_add" />
 
-    <TextView
-        android:id="@+id/add_new_sound_text"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:minHeight="?android:attr/listPreferredItemHeightSmall"
-        android:text="@null"
-        android:textColor="?android:attr/colorAccent"
-        android:textAppearance="?android:attr/textAppearanceMedium"
-        android:maxLines="3"
-        android:gravity="center_vertical"
-        android:paddingEnd="?android:attr/dialogPreferredPadding"
-        android:drawablePadding="20dp"
-        android:ellipsize="marquee"/>
+    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
+              android:id="@+id/add_new_sound_text"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:minHeight="?android:attr/listPreferredItemHeightSmall"
+              android:text="@null"
+              android:textColor="?android:attr/colorAccent"
+              android:textAppearance="?android:attr/textAppearanceMedium"
+              android:maxLines="3"
+              android:gravity="center_vertical"
+              android:paddingEnd="?android:attr/dialogPreferredPadding"
+              android:drawablePadding="20dp"
+              android:ellipsize="marquee" />
 </LinearLayout>
\ No newline at end of file
diff --git a/packages/SoundPicker/res/layout/radio_with_work_badge.xml b/packages/SoundPicker/res/layout/radio_with_work_badge.xml
index 36ac93e..2e44b6f 100644
--- a/packages/SoundPicker/res/layout/radio_with_work_badge.xml
+++ b/packages/SoundPicker/res/layout/radio_with_work_badge.xml
@@ -14,14 +14,12 @@
      limitations under the License.
 -->
 
-<com.android.soundpicker.CheckedListItem
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="wrap_content"
-    android:gravity="center_vertical"
-    android:background="?android:attr/selectableItemBackground"
-    android:focusable="true"
-    android:clickable="true">
+<com.android.soundpicker.CheckedListItem xmlns:android="http://schemas.android.com/apk/res/android"
+     android:layout_width="fill_parent"
+     android:layout_height="wrap_content"
+     android:gravity="center_vertical"
+     android:background="?android:attr/selectableItemBackground"
+    >
 
     <CheckedTextView
         android:id="@+id/checked_text_view"
@@ -37,7 +35,7 @@
         android:drawablePadding="20dp"
         android:ellipsize="marquee"
         android:layout_toLeftOf="@+id/work_icon"
-        android:maxLines="3"/>
+        android:maxLines="3" />
 
     <ImageView
         android:id="@id/work_icon"
@@ -46,5 +44,5 @@
         android:layout_alignParentRight="true"
         android:layout_centerVertical="true"
         android:scaleType="centerCrop"
-        android:layout_marginRight="20dp"/>
-</com.android.soundpicker.CheckedListItem>
+        android:layout_marginRight="20dp" />
+</com.android.soundpicker.CheckedListItem>
\ No newline at end of file
diff --git a/packages/SoundPicker/res/values/strings.xml b/packages/SoundPicker/res/values/strings.xml
index ab7b95a..04a2c2b 100644
--- a/packages/SoundPicker/res/values/strings.xml
+++ b/packages/SoundPicker/res/values/strings.xml
@@ -40,8 +40,4 @@
 
     <!-- Text for the name of the app. [CHAR LIMIT=12] -->
     <string name="app_label">Sounds</string>
-
-    <string name="empty_list">The list is empty</string>
-    <string name="sound_page_title">Sound</string>
-    <string name="vibration_page_title">Vibration</string>
 </resources>
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
index 90a14f9..ea46c0c 100644
--- a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
@@ -16,19 +16,43 @@
 
 package com.android.soundpicker;
 
+import android.content.ContentProvider;
+import android.content.Context;
+import android.content.DialogInterface;
 import android.content.Intent;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.database.Cursor;
+import android.database.CursorWrapper;
+import android.media.AudioAttributes;
+import android.media.Ringtone;
 import android.media.RingtoneManager;
 import android.net.Uri;
+import android.os.AsyncTask;
 import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
 import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.MediaStore;
+import android.provider.Settings;
 import android.util.Log;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.CursorAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
 
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentTransaction;
-import androidx.lifecycle.ViewModelProvider;
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
 
-import dagger.hilt.android.AndroidEntryPoint;
+import java.io.IOException;
+import java.util.regex.Pattern;
 
 /**
  * The {@link RingtonePickerActivity} allows the user to choose one from all of the
@@ -36,183 +60,727 @@
  *
  * @see RingtoneManager#ACTION_RINGTONE_PICKER
  */
-@AndroidEntryPoint(AppCompatActivity.class)
-public final class RingtonePickerActivity extends Hilt_RingtonePickerActivity {
+public final class RingtonePickerActivity extends AlertActivity implements
+        AdapterView.OnItemSelectedListener, Runnable, DialogInterface.OnClickListener,
+        AlertController.AlertParams.OnPrepareListViewListener {
+
+    private static final int POS_UNKNOWN = -1;
 
     private static final String TAG = "RingtonePickerActivity";
-    // TODO: Use the extra keys from RingtoneManager once they're added.
-    private static final String EXTRA_RINGTONE_PICKER_CATEGORY = "EXTRA_RINGTONE_PICKER_CATEGORY";
-    private static final String EXTRA_VIBRATION_SHOW_DEFAULT = "EXTRA_VIBRATION_SHOW_DEFAULT";
-    private static final String EXTRA_VIBRATION_DEFAULT_URI = "EXTRA_VIBRATION_DEFAULT_URI";
-    private static final String EXTRA_VIBRATION_SHOW_SILENT = "EXTRA_VIBRATION_SHOW_SILENT";
-    private static final String EXTRA_VIBRATION_EXISTING_URI = "EXTRA_VIBRATION_EXISTING_URI";
-    private static final boolean RINGTONE_PICKER_CATEGORY_FEATURE_ENABLED = false;
 
-    private RingtonePickerViewModel mRingtonePickerViewModel;
+    private static final int DELAY_MS_SELECTION_PLAYED = 300;
+
+    private static final String COLUMN_LABEL = MediaStore.Audio.Media.TITLE;
+
+    private static final String SAVE_CLICKED_POS = "clicked_pos";
+
+    private static final String SOUND_NAME_RES_PREFIX = "sound_name_";
+
+    private static final int ADD_FILE_REQUEST_CODE = 300;
+
+    private RingtoneManager mRingtoneManager;
+    private int mType;
+
+    private Cursor mCursor;
+    private Handler mHandler;
+    private BadgedRingtoneAdapter mAdapter;
+
+    /** The position in the list of the 'Silent' item. */
+    private int mSilentPos = POS_UNKNOWN;
+
+    /** The position in the list of the 'Default' item. */
+    private int mDefaultRingtonePos = POS_UNKNOWN;
+
+    /** The position in the list of the ringtone to sample. */
+    private int mSampleRingtonePos = POS_UNKNOWN;
+
+    /** Whether this list has the 'Silent' item. */
+    private boolean mHasSilentItem;
+
+    /** The Uri to place a checkmark next to. */
+    private Uri mExistingUri;
+
+    /** The number of static items in the list. */
+    private int mStaticItemCount;
+
+    /** Whether this list has the 'Default' item. */
+    private boolean mHasDefaultItem;
+
+    /** The Uri to play when the 'Default' item is clicked. */
+    private Uri mUriForDefaultItem;
+
+    /** Id of the user to which the ringtone picker should list the ringtones */
+    private int mPickerUserId;
+
+    /** Context of the user specified by mPickerUserId */
+    private Context mTargetContext;
+
+    /**
+     * A Ringtone for the default ringtone. In most cases, the RingtoneManager
+     * will stop the previous ringtone. However, the RingtoneManager doesn't
+     * manage the default ringtone for us, so we should stop this one manually.
+     */
+    private Ringtone mDefaultRingtone;
+
+    /**
+     * The ringtone that's currently playing, unless the currently playing one is the default
+     * ringtone.
+     */
+    private Ringtone mCurrentRingtone;
+
+    /**
+     * Stable ID for the ringtone that is currently checked (may be -1 if no ringtone is checked).
+     */
+    private long mCheckedItemId = -1;
+
     private int mAttributesFlags;
 
+    private boolean mShowOkCancelButtons;
+
+    /**
+     * Keep the currently playing ringtone around when changing orientation, so that it
+     * can be stopped later, after the activity is recreated.
+     */
+    private static Ringtone sPlayingRingtone;
+
+    private DialogInterface.OnClickListener mRingtoneClickListener =
+            new DialogInterface.OnClickListener() {
+
+                /*
+                 * On item clicked
+                 */
+                public void onClick(DialogInterface dialog, int which) {
+                    if (which == mCursor.getCount() + mStaticItemCount) {
+                        // The "Add new ringtone" item was clicked. Start a file picker intent to select
+                        // only audio files (MIME type "audio/*")
+                        final Intent chooseFile = getMediaFilePickerIntent();
+                        startActivityForResult(chooseFile, ADD_FILE_REQUEST_CODE);
+                        return;
+                    }
+
+                    // Save the position of most recently clicked item
+                    setCheckedItem(which);
+
+                    // In the buttonless (watch-only) version, preemptively set our result since we won't
+                    // have another chance to do so before the activity closes.
+                    if (!mShowOkCancelButtons) {
+                        setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri());
+                    }
+
+                    // Play clip
+                    playRingtone(which, 0);
+                }
+
+            };
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_ringtone_picker);
 
-        mRingtonePickerViewModel = new ViewModelProvider(this).get(RingtonePickerViewModel.class);
+        mHandler = new Handler();
 
         Intent intent = getIntent();
-        /**
-         * Id of the user to which the ringtone picker should list the ringtones
-         */
-        int pickerUserId = UserHandle.myUserId();
+        mPickerUserId = UserHandle.myUserId();
+        mTargetContext = this;
 
         // Get the types of ringtones to show
-        int ringtoneType = intent.getIntExtra(RingtoneManager.EXTRA_RINGTONE_TYPE,
-                RingtonePickerViewModel.RINGTONE_TYPE_UNKNOWN);
+        mType = intent.getIntExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, -1);
+        initRingtoneManager();
 
+        /*
+         * Get whether to show the 'Default' item, and the URI to play when the
+         * default is clicked
+         */
+        mHasDefaultItem = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
+        mUriForDefaultItem = intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI);
+        if (mUriForDefaultItem == null) {
+            if (mType == RingtoneManager.TYPE_NOTIFICATION) {
+                mUriForDefaultItem = Settings.System.DEFAULT_NOTIFICATION_URI;
+            } else if (mType == RingtoneManager.TYPE_ALARM) {
+                mUriForDefaultItem = Settings.System.DEFAULT_ALARM_ALERT_URI;
+            } else if (mType == RingtoneManager.TYPE_RINGTONE) {
+                mUriForDefaultItem = Settings.System.DEFAULT_RINGTONE_URI;
+            } else {
+                // or leave it null for silence.
+                mUriForDefaultItem = Settings.System.DEFAULT_RINGTONE_URI;
+            }
+        }
+
+        // Get whether to show the 'Silent' item
+        mHasSilentItem = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
         // AudioAttributes flags
         mAttributesFlags |= intent.getIntExtra(
                 RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS,
                 0 /*defaultValue == no flags*/);
 
-        boolean showOkCancelButtons = getResources().getBoolean(R.bool.config_showOkCancelButtons);
-
-        String title = intent.getStringExtra(RingtoneManager.EXTRA_RINGTONE_TITLE);
-        if (title == null) {
-            title = getString(RingtonePickerViewModel.getTitleByType(ringtoneType));
-        }
-        String ringtonePickerCategory = intent.getStringExtra(EXTRA_RINGTONE_PICKER_CATEGORY);
-        RingtonePickerViewModel.PickerType pickerType = mapCategoryToPickerType(
-                ringtonePickerCategory);
-
-        RingtoneListHandler.Config soundListConfig = getSoundListConfig(pickerType, intent,
-                ringtoneType);
-        RingtoneListHandler.Config vibrationListConfig = getVibrationListConfig(pickerType, intent);
-
-        RingtonePickerViewModel.Config pickerConfig =
-                new RingtonePickerViewModel.Config(title, pickerUserId, ringtoneType,
-                        showOkCancelButtons, mAttributesFlags, pickerType);
-
-        mRingtonePickerViewModel.init(pickerConfig, soundListConfig, vibrationListConfig);
-
-        if (savedInstanceState == null) {
-            TabbedDialogFragment dialogFragment = new TabbedDialogFragment();
-
-            FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
-            Fragment prev = getSupportFragmentManager().findFragmentByTag(TabbedDialogFragment.TAG);
-            if (prev != null) {
-                ft.remove(prev);
-            }
-            ft.addToBackStack(null);
-            dialogFragment.show(ft, TabbedDialogFragment.TAG);
-        }
+        mShowOkCancelButtons = getResources().getBoolean(R.bool.config_showOkCancelButtons);
 
         // The volume keys will control the stream that we are choosing a ringtone for
-        setVolumeControlStream(mRingtonePickerViewModel.getRingtoneStreamType());
-    }
+        setVolumeControlStream(mRingtoneManager.inferStreamType());
 
-    private RingtoneListHandler.Config getSoundListConfig(
-            RingtonePickerViewModel.PickerType pickerType, Intent intent, int ringtoneType) {
-        if (pickerType != RingtonePickerViewModel.PickerType.SOUND_PICKER
-                && pickerType != RingtonePickerViewModel.PickerType.RINGTONE_PICKER) {
-            // This ringtone picker does not require a sound picker.
-            return null;
-        }
-
-        // Get whether to show the 'Default' sound item, and the URI to play when it's clicked
-        boolean hasDefaultSoundItem =
-                intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
-
-        // The Uri to play when the 'Default' sound item is clicked.
-        Uri uriForDefaultSoundItem =
-                intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI);
-        if (uriForDefaultSoundItem == null) {
-            uriForDefaultSoundItem = RingtonePickerViewModel.getDefaultItemUriByType(ringtoneType);
-        }
-
-        // Get whether this list has the 'Silent' sound item.
-        boolean hasSilentSoundItem =
-                intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
-
-        // AudioAttributes flags
-        mAttributesFlags |= intent.getIntExtra(
-                RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS,
-                0 /*defaultValue == no flags*/);
-
-        // Get the sound URI whose list item should have a checkmark
-        Uri existingSoundUri = intent
+        // Get the URI whose list item should have a checkmark
+        mExistingUri = intent
                 .getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI);
 
-        return new RingtoneListHandler.Config(hasDefaultSoundItem,
-                uriForDefaultSoundItem, hasSilentSoundItem, existingSoundUri);
-    }
-
-    private RingtoneListHandler.Config getVibrationListConfig(
-            RingtonePickerViewModel.PickerType pickerType, Intent intent) {
-        if (pickerType != RingtonePickerViewModel.PickerType.VIBRATION_PICKER
-                && pickerType != RingtonePickerViewModel.PickerType.RINGTONE_PICKER) {
-            // This ringtone picker does not require a vibration picker.
-            return null;
+        // Create the list of ringtones and hold on to it so we can update later.
+        mAdapter = new BadgedRingtoneAdapter(this, mCursor,
+                /* isManagedProfile = */ UserManager.get(this).isManagedProfile(mPickerUserId));
+        if (savedInstanceState != null) {
+            setCheckedItem(savedInstanceState.getInt(SAVE_CLICKED_POS, POS_UNKNOWN));
         }
 
-        // Get whether to show the 'Default' vibration item, and the URI to play when it's clicked
-        boolean hasDefaultVibrationItem =
-                intent.getBooleanExtra(EXTRA_VIBRATION_SHOW_DEFAULT, false);
+        final AlertController.AlertParams p = mAlertParams;
+        p.mAdapter = mAdapter;
+        p.mOnClickListener = mRingtoneClickListener;
+        p.mLabelColumn = COLUMN_LABEL;
+        p.mIsSingleChoice = true;
+        p.mOnItemSelectedListener = this;
+        if (mShowOkCancelButtons) {
+            p.mPositiveButtonText = getString(com.android.internal.R.string.ok);
+            p.mPositiveButtonListener = this;
+            p.mNegativeButtonText = getString(com.android.internal.R.string.cancel);
+            p.mPositiveButtonListener = this;
+        }
+        p.mOnPrepareListViewListener = this;
 
-        // The Uri to play when the 'Default' vibration item is clicked.
-        Uri uriForDefaultVibrationItem = intent.getParcelableExtra(EXTRA_VIBRATION_DEFAULT_URI);
+        p.mTitle = intent.getCharSequenceExtra(RingtoneManager.EXTRA_RINGTONE_TITLE);
+        if (p.mTitle == null) {
+            if (mType == RingtoneManager.TYPE_ALARM) {
+                p.mTitle = getString(com.android.internal.R.string.ringtone_picker_title_alarm);
+            } else if (mType == RingtoneManager.TYPE_NOTIFICATION) {
+                p.mTitle =
+                        getString(com.android.internal.R.string.ringtone_picker_title_notification);
+            } else {
+                p.mTitle = getString(com.android.internal.R.string.ringtone_picker_title);
+            }
+        }
 
-        // Get whether this list has the 'Silent' vibration item.
-        boolean hasSilentVibrationItem =
-                intent.getBooleanExtra(EXTRA_VIBRATION_SHOW_SILENT, true);
+        setupAlert();
 
-        // Get the vibration URI whose list item should have a checkmark
-        Uri existingVibrationUri = intent.getParcelableExtra(EXTRA_VIBRATION_EXISTING_URI);
-
-        return new RingtoneListHandler.Config(
-                hasDefaultVibrationItem, uriForDefaultVibrationItem, hasSilentVibrationItem,
-                existingVibrationUri);
+        ListView listView = mAlert.getListView();
+        if (listView != null) {
+            // List view needs to gain focus in order for RSB to work.
+            if (!listView.requestFocus()) {
+                Log.e(TAG, "Unable to gain focus! RSB may not work properly.");
+            }
+        }
+    }
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt(SAVE_CLICKED_POS, getCheckedItem());
     }
 
     @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+
+        if (requestCode == ADD_FILE_REQUEST_CODE && resultCode == RESULT_OK) {
+            // Add the custom ringtone in a separate thread
+            final AsyncTask<Uri, Void, Uri> installTask = new AsyncTask<Uri, Void, Uri>() {
+                @Override
+                protected Uri doInBackground(Uri... params) {
+                    try {
+                        return mRingtoneManager.addCustomExternalRingtone(params[0], mType);
+                    } catch (IOException | IllegalArgumentException e) {
+                        Log.e(TAG, "Unable to add new ringtone", e);
+                    }
+                    return null;
+                }
+
+                @Override
+                protected void onPostExecute(Uri ringtoneUri) {
+                    if (ringtoneUri != null) {
+                        requeryForAdapter();
+                    } else {
+                        // Ringtone was not added, display error Toast
+                        Toast.makeText(RingtonePickerActivity.this, R.string.unable_to_add_ringtone,
+                                Toast.LENGTH_SHORT).show();
+                    }
+                }
+            };
+            installTask.execute(data.getData());
+        }
+    }
+
+    // Disabled because context menus aren't Material Design :(
+    /*
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
+        int position = ((AdapterContextMenuInfo) menuInfo).position;
+
+        Ringtone ringtone = getRingtone(getRingtoneManagerPosition(position));
+        if (ringtone != null && mRingtoneManager.isCustomRingtone(ringtone.getUri())) {
+            // It's a custom ringtone so we display the context menu
+            menu.setHeaderTitle(ringtone.getTitle(this));
+            menu.add(Menu.NONE, Menu.FIRST, Menu.NONE, R.string.delete_ringtone_text);
+        }
+    }
+
+    @Override
+    public boolean onContextItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case Menu.FIRST: {
+                int deletedRingtonePos = ((AdapterContextMenuInfo) item.getMenuInfo()).position;
+                Uri deletedRingtoneUri = getRingtone(
+                        getRingtoneManagerPosition(deletedRingtonePos)).getUri();
+                if(mRingtoneManager.deleteExternalRingtone(deletedRingtoneUri)) {
+                    requeryForAdapter();
+                } else {
+                    Toast.makeText(this, R.string.unable_to_delete_ringtone, Toast.LENGTH_SHORT)
+                            .show();
+                }
+                return true;
+            }
+            default: {
+                return false;
+            }
+        }
+    }
+    */
+
+    @Override
     public void onDestroy() {
-        mRingtonePickerViewModel.cancelPendingAsyncTasks();
+        if (mHandler != null) {
+            mHandler.removeCallbacksAndMessages(null);
+        }
+        if (mCursor != null) {
+            mCursor.close();
+            mCursor = null;
+        }
         super.onDestroy();
     }
 
+    public void onPrepareListView(ListView listView) {
+        // Reset the static item count, as this method can be called multiple times
+        mStaticItemCount = 0;
+
+        if (mHasDefaultItem) {
+            mDefaultRingtonePos = addDefaultRingtoneItem(listView);
+
+            if (getCheckedItem() == POS_UNKNOWN && RingtoneManager.isDefault(mExistingUri)) {
+                setCheckedItem(mDefaultRingtonePos);
+            }
+        }
+
+        if (mHasSilentItem) {
+            mSilentPos = addSilentItem(listView);
+
+            // The 'Silent' item should use a null Uri
+            if (getCheckedItem() == POS_UNKNOWN && mExistingUri == null) {
+                setCheckedItem(mSilentPos);
+            }
+        }
+
+        if (getCheckedItem() == POS_UNKNOWN) {
+            setCheckedItem(getListPosition(mRingtoneManager.getRingtonePosition(mExistingUri)));
+        }
+
+        // In the buttonless (watch-only) version, preemptively set our result since we won't
+        // have another chance to do so before the activity closes.
+        if (!mShowOkCancelButtons) {
+            setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri());
+        }
+        // If external storage is available, add a button to install sounds from storage.
+        if (resolvesMediaFilePicker()
+                && Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+            addNewSoundItem(listView);
+        }
+
+        // Enable context menu in ringtone items
+        registerForContextMenu(listView);
+    }
+
+    /**
+     * Re-query RingtoneManager for the most recent set of installed ringtones. May move the
+     * selected item position to match the new position of the chosen sound.
+     *
+     * This should only need to happen after adding or removing a ringtone.
+     */
+    private void requeryForAdapter() {
+        // Refresh and set a new cursor, closing the old one.
+        initRingtoneManager();
+        mAdapter.changeCursor(mCursor);
+
+        // Update checked item location.
+        int checkedPosition = POS_UNKNOWN;
+        for (int i = 0; i < mAdapter.getCount(); i++) {
+            if (mAdapter.getItemId(i) == mCheckedItemId) {
+                checkedPosition = getListPosition(i);
+                break;
+            }
+        }
+        if (mHasSilentItem && checkedPosition == POS_UNKNOWN) {
+            checkedPosition = mSilentPos;
+        }
+        setCheckedItem(checkedPosition);
+        setupAlert();
+    }
+
+    /**
+     * Adds a static item to the top of the list. A static item is one that is not from the
+     * RingtoneManager.
+     *
+     * @param listView The ListView to add to.
+     * @param textResId The resource ID of the text for the item.
+     * @return The position of the inserted item.
+     */
+    private int addStaticItem(ListView listView, int textResId) {
+        TextView textView = (TextView) getLayoutInflater().inflate(
+                com.android.internal.R.layout.select_dialog_singlechoice_material, listView, false);
+        textView.setText(textResId);
+        listView.addHeaderView(textView);
+        mStaticItemCount++;
+        return listView.getHeaderViewsCount() - 1;
+    }
+
+    private int addDefaultRingtoneItem(ListView listView) {
+        if (mType == RingtoneManager.TYPE_NOTIFICATION) {
+            return addStaticItem(listView, R.string.notification_sound_default);
+        } else if (mType == RingtoneManager.TYPE_ALARM) {
+            return addStaticItem(listView, R.string.alarm_sound_default);
+        }
+
+        return addStaticItem(listView, R.string.ringtone_default);
+    }
+
+    private int addSilentItem(ListView listView) {
+        return addStaticItem(listView, com.android.internal.R.string.ringtone_silent);
+    }
+
+    private void addNewSoundItem(ListView listView) {
+        View view = getLayoutInflater().inflate(R.layout.add_new_sound_item, listView,
+                false /* attachToRoot */);
+        TextView text = (TextView)view.findViewById(R.id.add_new_sound_text);
+
+        if (mType == RingtoneManager.TYPE_ALARM) {
+            text.setText(R.string.add_alarm_text);
+        } else if (mType == RingtoneManager.TYPE_NOTIFICATION) {
+            text.setText(R.string.add_notification_text);
+        } else {
+            text.setText(R.string.add_ringtone_text);
+        }
+        listView.addFooterView(view);
+    }
+
+    private void initRingtoneManager() {
+        // Reinstantiate the RingtoneManager. Cursor.requery() was deprecated and calling it
+        // causes unexpected behavior.
+        mRingtoneManager = new RingtoneManager(mTargetContext, /* includeParentRingtones */ true);
+        if (mType != -1) {
+            mRingtoneManager.setType(mType);
+        }
+        mCursor = new LocalizedCursor(mRingtoneManager.getCursor(), getResources(), COLUMN_LABEL);
+    }
+
+    private Ringtone getRingtone(int ringtoneManagerPosition) {
+        if (ringtoneManagerPosition < 0) {
+            return null;
+        }
+        return mRingtoneManager.getRingtone(ringtoneManagerPosition);
+    }
+
+    private int getCheckedItem() {
+        return mAlertParams.mCheckedItem;
+    }
+
+    private void setCheckedItem(int pos) {
+        mAlertParams.mCheckedItem = pos;
+        mCheckedItemId = mAdapter.getItemId(getRingtoneManagerPosition(pos));
+    }
+
+    /*
+     * On click of Ok/Cancel buttons
+     */
+    public void onClick(DialogInterface dialog, int which) {
+        boolean positiveResult = which == DialogInterface.BUTTON_POSITIVE;
+
+        // Stop playing the previous ringtone
+        mRingtoneManager.stopPreviousRingtone();
+
+        if (positiveResult) {
+            setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri());
+        } else {
+            setResult(RESULT_CANCELED);
+        }
+
+        finish();
+    }
+
+    /*
+     * On item selected via keys
+     */
+    public void onItemSelected(AdapterView parent, View view, int position, long id) {
+        // footer view
+        if (position >= mCursor.getCount() + mStaticItemCount) {
+            return;
+        }
+
+        playRingtone(position, DELAY_MS_SELECTION_PLAYED);
+
+        // In the buttonless (watch-only) version, preemptively set our result since we won't
+        // have another chance to do so before the activity closes.
+        if (!mShowOkCancelButtons) {
+            setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri());
+        }
+    }
+
+    public void onNothingSelected(AdapterView parent) {
+    }
+
+    private void playRingtone(int position, int delayMs) {
+        mHandler.removeCallbacks(this);
+        mSampleRingtonePos = position;
+        mHandler.postDelayed(this, delayMs);
+    }
+
+    public void run() {
+        stopAnyPlayingRingtone();
+        if (mSampleRingtonePos == mSilentPos) {
+            return;
+        }
+
+        Ringtone ringtone;
+        if (mSampleRingtonePos == mDefaultRingtonePos) {
+            if (mDefaultRingtone == null) {
+                mDefaultRingtone = RingtoneManager.getRingtone(this, mUriForDefaultItem);
+            }
+            /*
+             * Stream type of mDefaultRingtone is not set explicitly here.
+             * It should be set in accordance with mRingtoneManager of this Activity.
+             */
+            if (mDefaultRingtone != null) {
+                mDefaultRingtone.setStreamType(mRingtoneManager.inferStreamType());
+            }
+            ringtone = mDefaultRingtone;
+            mCurrentRingtone = null;
+        } else {
+            ringtone = mRingtoneManager.getRingtone(getRingtoneManagerPosition(mSampleRingtonePos));
+            mCurrentRingtone = ringtone;
+        }
+
+        if (ringtone != null) {
+            if (mAttributesFlags != 0) {
+                ringtone.setAudioAttributes(
+                        new AudioAttributes.Builder(ringtone.getAudioAttributes())
+                                .setFlags(mAttributesFlags)
+                                .build());
+            }
+            ringtone.play();
+        }
+    }
+
     @Override
     protected void onStop() {
         super.onStop();
-        mRingtonePickerViewModel.onStop(isChangingConfigurations());
+
+        if (!isChangingConfigurations()) {
+            stopAnyPlayingRingtone();
+        } else {
+            saveAnyPlayingRingtone();
+        }
     }
 
     @Override
     protected void onPause() {
         super.onPause();
-        mRingtonePickerViewModel.onPause(isChangingConfigurations());
-    }
-
-    /**
-     * Maps the ringtone picker category to the appropriate PickerType.
-     * If the category is null or the feature is still not released, then it defaults to sound
-     * picker.
-     *
-     * @param category the ringtone picker category.
-     * @return the corresponding picker type.
-     */
-    private static RingtonePickerViewModel.PickerType mapCategoryToPickerType(String category) {
-        if (category == null || !RINGTONE_PICKER_CATEGORY_FEATURE_ENABLED) {
-            return RingtonePickerViewModel.PickerType.SOUND_PICKER;
-        }
-
-        switch (category) {
-            case "android.intent.category.RINGTONE_PICKER_RINGTONE":
-                return RingtonePickerViewModel.PickerType.RINGTONE_PICKER;
-            case "android.intent.category.RINGTONE_PICKER_SOUND":
-                return RingtonePickerViewModel.PickerType.SOUND_PICKER;
-            case "android.intent.category.RINGTONE_PICKER_VIBRATION":
-                return RingtonePickerViewModel.PickerType.VIBRATION_PICKER;
-            default:
-                Log.w(TAG, "Unrecognized category: " + category + ". Defaulting to sound picker.");
-                return RingtonePickerViewModel.PickerType.SOUND_PICKER;
+        if (!isChangingConfigurations()) {
+            stopAnyPlayingRingtone();
         }
     }
-}
+
+    private void setSuccessResultWithRingtone(Uri ringtoneUri) {
+        setResult(RESULT_OK,
+                new Intent().putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, ringtoneUri));
+    }
+
+    private Uri getCurrentlySelectedRingtoneUri() {
+        if (getCheckedItem() == POS_UNKNOWN) {
+            // When the getCheckItem is POS_UNKNOWN, it is not the case we expected.
+            // We return null for this case.
+            return null;
+        } else if (getCheckedItem() == mDefaultRingtonePos) {
+            // Use the default Uri that they originally gave us.
+            return mUriForDefaultItem;
+        } else if (getCheckedItem() == mSilentPos) {
+            // Use a null Uri for the 'Silent' item.
+            return null;
+        } else {
+            return mRingtoneManager.getRingtoneUri(getRingtoneManagerPosition(getCheckedItem()));
+        }
+    }
+
+    private void saveAnyPlayingRingtone() {
+        if (mDefaultRingtone != null && mDefaultRingtone.isPlaying()) {
+            sPlayingRingtone = mDefaultRingtone;
+        } else if (mCurrentRingtone != null && mCurrentRingtone.isPlaying()) {
+            sPlayingRingtone = mCurrentRingtone;
+        }
+    }
+
+    private void stopAnyPlayingRingtone() {
+        if (sPlayingRingtone != null && sPlayingRingtone.isPlaying()) {
+            sPlayingRingtone.stop();
+        }
+        sPlayingRingtone = null;
+
+        if (mDefaultRingtone != null && mDefaultRingtone.isPlaying()) {
+            mDefaultRingtone.stop();
+        }
+
+        if (mRingtoneManager != null) {
+            mRingtoneManager.stopPreviousRingtone();
+        }
+    }
+
+    private int getRingtoneManagerPosition(int listPos) {
+        return listPos - mStaticItemCount;
+    }
+
+    private int getListPosition(int ringtoneManagerPos) {
+
+        // If the manager position is -1 (for not found), return that
+        if (ringtoneManagerPos < 0) return ringtoneManagerPos;
+
+        return ringtoneManagerPos + mStaticItemCount;
+    }
+
+    private Intent getMediaFilePickerIntent() {
+        final Intent chooseFile = new Intent(Intent.ACTION_GET_CONTENT);
+        chooseFile.setType("audio/*");
+        chooseFile.putExtra(Intent.EXTRA_MIME_TYPES,
+                new String[] { "audio/*", "application/ogg" });
+        return chooseFile;
+    }
+
+    private boolean resolvesMediaFilePicker() {
+        return getMediaFilePickerIntent().resolveActivity(getPackageManager()) != null;
+    }
+
+    private static class LocalizedCursor extends CursorWrapper {
+
+        final int mTitleIndex;
+        final Resources mResources;
+        String mNamePrefix;
+        final Pattern mSanitizePattern;
+
+        LocalizedCursor(Cursor cursor, Resources resources, String columnLabel) {
+            super(cursor);
+            mTitleIndex = mCursor.getColumnIndex(columnLabel);
+            mResources = resources;
+            mSanitizePattern = Pattern.compile("[^a-zA-Z0-9]");
+            if (mTitleIndex == -1) {
+                Log.e(TAG, "No index for column " + columnLabel);
+                mNamePrefix = null;
+            } else {
+                try {
+                    // Build the prefix for the name of the resource to look up
+                    // format is: "ResourcePackageName::ResourceTypeName/"
+                    // (the type name is expected to be "string" but let's not hardcode it).
+                    // Here we use an existing resource "notification_sound_default" which is
+                    // always expected to be found.
+                    mNamePrefix = String.format("%s:%s/%s",
+                            mResources.getResourcePackageName(R.string.notification_sound_default),
+                            mResources.getResourceTypeName(R.string.notification_sound_default),
+                            SOUND_NAME_RES_PREFIX);
+                } catch (NotFoundException e) {
+                    mNamePrefix = null;
+                }
+            }
+        }
+
+        /**
+         * Process resource name to generate a valid resource name.
+         * @param input
+         * @return a non-null String
+         */
+        private String sanitize(String input) {
+            if (input == null) {
+                return "";
+            }
+            return mSanitizePattern.matcher(input).replaceAll("_").toLowerCase();
+        }
+
+        @Override
+        public String getString(int columnIndex) {
+            final String defaultName = mCursor.getString(columnIndex);
+            if ((columnIndex != mTitleIndex) || (mNamePrefix == null)) {
+                return defaultName;
+            }
+            TypedValue value = new TypedValue();
+            try {
+                // the name currently in the database is used to derive a name to match
+                // against resource names in this package
+                mResources.getValue(mNamePrefix + sanitize(defaultName), value, false);
+            } catch (NotFoundException e) {
+                // no localized string, use the default string
+                return defaultName;
+            }
+            if ((value != null) && (value.type == TypedValue.TYPE_STRING)) {
+                Log.d(TAG, String.format("Replacing name %s with %s",
+                        defaultName, value.string.toString()));
+                return value.string.toString();
+            } else {
+                Log.e(TAG, "Invalid value when looking up localized name, using " + defaultName);
+                return defaultName;
+            }
+        }
+    }
+
+    private class BadgedRingtoneAdapter extends CursorAdapter {
+        private final boolean mIsManagedProfile;
+
+        public BadgedRingtoneAdapter(Context context, Cursor cursor, boolean isManagedProfile) {
+            super(context, cursor);
+            mIsManagedProfile = isManagedProfile;
+        }
+
+        @Override
+        public long getItemId(int position) {
+            if (position < 0) {
+                return position;
+            }
+            return super.getItemId(position);
+        }
+
+        @Override
+        public View newView(Context context, Cursor cursor, ViewGroup parent) {
+            LayoutInflater inflater = LayoutInflater.from(context);
+            return inflater.inflate(R.layout.radio_with_work_badge, parent, false);
+        }
+
+        @Override
+        public void bindView(View view, Context context, Cursor cursor) {
+            // Set text as the title of the ringtone
+            ((TextView) view.findViewById(R.id.checked_text_view))
+                    .setText(cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX));
+
+            boolean isWorkRingtone = false;
+            if (mIsManagedProfile) {
+                /*
+                 * Display the work icon if the ringtone belongs to a work profile. We can tell that
+                 * a ringtone belongs to a work profile if the picker user is a managed profile, the
+                 * ringtone Uri is in external storage, and either the uri has no user id or has the
+                 * id of the picker user
+                 */
+                Uri currentUri = mRingtoneManager.getRingtoneUri(cursor.getPosition());
+                int uriUserId = ContentProvider.getUserIdFromUri(currentUri, mPickerUserId);
+                Uri uriWithoutUserId = ContentProvider.getUriWithoutUserId(currentUri);
+
+                if (uriUserId == mPickerUserId && uriWithoutUserId.toString()
+                        .startsWith(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString())) {
+                    isWorkRingtone = true;
+                }
+            }
+
+            ImageView workIcon = (ImageView) view.findViewById(R.id.work_icon);
+            if(isWorkRingtone) {
+                workIcon.setImageDrawable(getPackageManager().getUserBadgeForDensityNoBackground(
+                        UserHandle.of(mPickerUserId), -1 /* density */));
+                workIcon.setVisibility(View.VISIBLE);
+            } else {
+                workIcon.setVisibility(View.GONE);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SoundPicker2/Android.bp b/packages/SoundPicker2/Android.bp
new file mode 100644
index 0000000..f4d8bf2
--- /dev/null
+++ b/packages/SoundPicker2/Android.bp
@@ -0,0 +1,46 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_library {
+    name: "SoundPicker2Lib",
+    srcs: [
+        "src/**/*.java",
+    ],
+    resource_dirs: [
+        "res",
+    ],
+    static_libs: [
+        "androidx.appcompat_appcompat",
+        "hilt_android",
+        "guava",
+        "androidx.recyclerview_recyclerview",
+        "androidx-constraintlayout_constraintlayout",
+        "androidx.viewpager2_viewpager2",
+        "com.google.android.material_material",
+    ],
+}
+
+android_app {
+    name: "SoundPicker2",
+    defaults: ["platform_app_defaults"],
+    manifest: "AndroidManifest.xml",
+    static_libs: ["SoundPicker2Lib"],
+    platform_apis: true,
+    certificate: "media",
+    privileged: true,
+
+    optimize: {
+        enabled: true,
+        optimize: true,
+        shrink: true,
+        shrink_resources: true,
+        obfuscate: false,
+        proguard_compatibility: false,
+    },
+}
diff --git a/packages/SoundPicker2/AndroidManifest.xml b/packages/SoundPicker2/AndroidManifest.xml
new file mode 100644
index 0000000..934b003
--- /dev/null
+++ b/packages/SoundPicker2/AndroidManifest.xml
@@ -0,0 +1,43 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.soundpicker"
+        android:sharedUserId="android.media">
+
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+    <uses-permission android:name="android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY" />
+    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+
+    <application
+            android:name=".RingtonePickerApplication"
+            android:allowBackup="false"
+            android:label="@string/app_label"
+            android:theme="@style/Theme.AppCompat"
+            android:supportsRtl="true">
+        <receiver android:name="RingtoneReceiver"
+                android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY"/>
+            </intent-filter>
+        </receiver>
+
+        <service android:name="RingtoneOverlayService" />
+
+        <activity android:name="RingtonePickerActivity"
+                android:theme="@style/Theme.AppCompat.Dialog"
+                android:enabled="@*android:bool/config_defaultRingtonePickerEnabled"
+                android:excludeFromRecents="true"
+                android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.RINGTONE_PICKER" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.RINGTONE_PICKER_SOUND" />
+                <category android:name="android.intent.category.RINGTONE_PICKER_VIBRATION" />
+                <category android:name="android.intent.category.RINGTONE_PICKER_RINGTONE" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/packages/SoundPicker2/OWNERS b/packages/SoundPicker2/OWNERS
new file mode 100644
index 0000000..5bf46e0
--- /dev/null
+++ b/packages/SoundPicker2/OWNERS
@@ -0,0 +1,2 @@
+# Haptics team works on the SoundPicker
+include platform/frameworks/base:/services/core/java/com/android/server/vibrator/OWNERS
diff --git a/packages/SoundPicker2/res/drawable/ic_add.xml b/packages/SoundPicker2/res/drawable/ic_add.xml
new file mode 100644
index 0000000..22b3fe9
--- /dev/null
+++ b/packages/SoundPicker2/res/drawable/ic_add.xml
@@ -0,0 +1,24 @@
+<!--
+    Copyright (C) 2016 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24.0dp"
+        android:height="24.0dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
+    <path
+        android:fillColor="?android:attr/colorAccent"
+        android:pathData="M38.0,26.0L26.0,26.0l0.0,12.0l-4.0,0.0L22.0,26.0L10.0,26.0l0.0,-4.0l12.0,0.0L22.0,10.0l4.0,0.0l0.0,12.0l12.0,0.0l0.0,4.0z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SoundPicker2/res/drawable/ic_add_padded.xml b/packages/SoundPicker2/res/drawable/ic_add_padded.xml
new file mode 100644
index 0000000..c376867
--- /dev/null
+++ b/packages/SoundPicker2/res/drawable/ic_add_padded.xml
@@ -0,0 +1,22 @@
+<!--
+    Copyright (C) 2017 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+        android:drawable="@drawable/ic_add"
+        android:insetTop="4dp"
+        android:insetRight="4dp"
+        android:insetBottom="4dp"
+        android:insetLeft="4dp"/>
diff --git a/packages/SoundPicker2/res/layout-watch/add_new_sound_item.xml b/packages/SoundPicker2/res/layout-watch/add_new_sound_item.xml
new file mode 100644
index 0000000..edfc0ab
--- /dev/null
+++ b/packages/SoundPicker2/res/layout-watch/add_new_sound_item.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!--
+     Currently, no file manager app on watch could handle ACTION_GET_CONTENT intent.
+     Make the visibility to "gone" to prevent failures.
+ -->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/add_new_sound_text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="?android:attr/listPreferredItemHeightSmall"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:text="@null"
+        android:textColor="?android:attr/colorAccent"
+        android:gravity="center_vertical"
+        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+        android:drawableStart="@drawable/ic_add_padded"
+        android:drawablePadding="8dp"
+        android:ellipsize="marquee"
+        android:visibility="gone" />
diff --git a/packages/SoundPicker2/res/layout-watch/radio_with_work_badge.xml b/packages/SoundPicker2/res/layout-watch/radio_with_work_badge.xml
new file mode 100644
index 0000000..ee29a37
--- /dev/null
+++ b/packages/SoundPicker2/res/layout-watch/radio_with_work_badge.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<com.android.soundpicker.CheckedListItem xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_vertical"
+    android:background="?android:attr/selectableItemBackground"
+    >
+
+    <CheckedTextView
+        android:id="@+id/checked_text_view"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="?android:attr/listPreferredItemHeightSmall"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textColor="?android:attr/textColorAlertDialogListItem"
+        android:gravity="center_vertical"
+        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+        android:drawableStart="?android:attr/listChoiceIndicatorSingle"
+        android:drawablePadding="8dp"
+        android:ellipsize="marquee"
+        android:layout_toLeftOf="@+id/work_icon"
+        android:maxLines="3" />
+
+    <ImageView
+        android:id="@id/work_icon"
+        android:layout_width="18dp"
+        android:layout_height="18dp"
+        android:layout_alignParentRight="true"
+        android:layout_centerVertical="true"
+        android:scaleType="centerCrop"
+        android:layout_marginRight="20dp" />
+</com.android.soundpicker.CheckedListItem>
diff --git a/packages/SoundPicker/res/layout/activity_ringtone_picker.xml b/packages/SoundPicker2/res/layout/activity_ringtone_picker.xml
similarity index 100%
rename from packages/SoundPicker/res/layout/activity_ringtone_picker.xml
rename to packages/SoundPicker2/res/layout/activity_ringtone_picker.xml
diff --git a/packages/SoundPicker2/res/layout/add_new_sound_item.xml b/packages/SoundPicker2/res/layout/add_new_sound_item.xml
new file mode 100644
index 0000000..024b97e
--- /dev/null
+++ b/packages/SoundPicker2/res/layout/add_new_sound_item.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="fill_parent"
+              android:layout_height="wrap_content"
+              android:gravity="center_vertical"
+              android:background="?android:attr/selectableItemBackground"
+              android:focusable="true"
+              android:clickable="true">
+
+    <ImageView
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:layout_alignParentRight="true"
+        android:layout_centerVertical="true"
+        android:scaleType="centerCrop"
+        android:layout_marginRight="24dp"
+        android:layout_marginLeft="24dp"
+        android:src="@drawable/ic_add"/>
+
+    <TextView
+        android:id="@+id/add_new_sound_text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="?android:attr/listPreferredItemHeightSmall"
+        android:text="@null"
+        android:textColor="?android:attr/colorAccent"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:maxLines="3"
+        android:gravity="center_vertical"
+        android:paddingEnd="?android:attr/dialogPreferredPadding"
+        android:drawablePadding="20dp"
+        android:ellipsize="marquee"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SoundPicker/res/layout/fragment_ringtone_picker.xml b/packages/SoundPicker2/res/layout/fragment_ringtone_picker.xml
similarity index 100%
rename from packages/SoundPicker/res/layout/fragment_ringtone_picker.xml
rename to packages/SoundPicker2/res/layout/fragment_ringtone_picker.xml
diff --git a/packages/SoundPicker/res/layout/fragment_tabbed_dialog.xml b/packages/SoundPicker2/res/layout/fragment_tabbed_dialog.xml
similarity index 100%
rename from packages/SoundPicker/res/layout/fragment_tabbed_dialog.xml
rename to packages/SoundPicker2/res/layout/fragment_tabbed_dialog.xml
diff --git a/packages/SoundPicker2/res/layout/radio_with_work_badge.xml b/packages/SoundPicker2/res/layout/radio_with_work_badge.xml
new file mode 100644
index 0000000..36ac93e
--- /dev/null
+++ b/packages/SoundPicker2/res/layout/radio_with_work_badge.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<com.android.soundpicker.CheckedListItem
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_vertical"
+    android:background="?android:attr/selectableItemBackground"
+    android:focusable="true"
+    android:clickable="true">
+
+    <CheckedTextView
+        android:id="@+id/checked_text_view"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="?android:attr/listPreferredItemHeightSmall"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textColor="?android:attr/textColorAlertDialogListItem"
+        android:gravity="center_vertical"
+        android:paddingStart="20dp"
+        android:paddingEnd="?android:attr/dialogPreferredPadding"
+        android:drawableStart="?android:attr/listChoiceIndicatorSingle"
+        android:drawablePadding="20dp"
+        android:ellipsize="marquee"
+        android:layout_toLeftOf="@+id/work_icon"
+        android:maxLines="3"/>
+
+    <ImageView
+        android:id="@id/work_icon"
+        android:layout_width="18dp"
+        android:layout_height="18dp"
+        android:layout_alignParentRight="true"
+        android:layout_centerVertical="true"
+        android:scaleType="centerCrop"
+        android:layout_marginRight="20dp"/>
+</com.android.soundpicker.CheckedListItem>
diff --git a/packages/SoundPicker2/res/raw/default_alarm_alert.ogg b/packages/SoundPicker2/res/raw/default_alarm_alert.ogg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/SoundPicker2/res/raw/default_alarm_alert.ogg
diff --git a/packages/SoundPicker2/res/raw/default_notification_sound.ogg b/packages/SoundPicker2/res/raw/default_notification_sound.ogg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/SoundPicker2/res/raw/default_notification_sound.ogg
diff --git a/packages/SoundPicker2/res/raw/default_ringtone.ogg b/packages/SoundPicker2/res/raw/default_ringtone.ogg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/SoundPicker2/res/raw/default_ringtone.ogg
diff --git a/packages/SoundPicker2/res/values/config.xml b/packages/SoundPicker2/res/values/config.xml
new file mode 100644
index 0000000..4e237a2
--- /dev/null
+++ b/packages/SoundPicker2/res/values/config.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds.  Do not translate.
+
+     NOTE: The naming convention is "config_camelCaseValue".  -->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- True if the ringtone picker should show the ok/cancel buttons. If it is not shown, the
+    ringtone will be automatically selected when the picker is closed. -->
+    <bool name="config_showOkCancelButtons">true</bool>
+</resources>
diff --git a/packages/SoundPicker2/res/values/strings.xml b/packages/SoundPicker2/res/values/strings.xml
new file mode 100644
index 0000000..ab7b95a
--- /dev/null
+++ b/packages/SoundPicker2/res/values/strings.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Choice in the ringtone picker.  If chosen, the default ringtone will be used. -->
+    <string name="ringtone_default">Default ringtone</string>
+
+    <!-- Choice in the notification sound picker.  If chosen, the default notification sound will be
+         used. -->
+    <string name="notification_sound_default">Default notification sound</string>
+
+    <!-- Choice in the alarm sound picker.  If chosen, the default alarm sound will be used. -->
+    <string name="alarm_sound_default">Default alarm sound</string>
+
+    <!-- Text for the RingtonePicker item that allows adding a new ringtone. -->
+    <string name="add_ringtone_text">Add ringtone</string>
+    <!-- Text for the RingtonePicker item that allows adding a new alarm. -->
+    <string name="add_alarm_text">Add alarm</string>
+    <!-- Text for the RingtonePicker item that allows adding a new notification. -->
+    <string name="add_notification_text">Add notification</string>
+    <!-- Text for the RingtonePicker item ContextMenu that allows deleting a custom ringtone. -->
+    <string name="delete_ringtone_text">Delete</string>
+    <!-- Text for the Toast displayed when adding a custom ringtone fails. -->
+    <string name="unable_to_add_ringtone">Unable to add custom ringtone</string>
+    <!-- Text for the Toast displayed when deleting a custom ringtone fails. -->
+    <string name="unable_to_delete_ringtone">Unable to delete custom ringtone</string>
+
+    <!-- Text for the name of the app. [CHAR LIMIT=12] -->
+    <string name="app_label">Sounds</string>
+
+    <string name="empty_list">The list is empty</string>
+    <string name="sound_page_title">Sound</string>
+    <string name="vibration_page_title">Vibration</string>
+</resources>
diff --git a/packages/SoundPicker2/res/values/styles.xml b/packages/SoundPicker2/res/values/styles.xml
new file mode 100644
index 0000000..d22d9c4
--- /dev/null
+++ b/packages/SoundPicker2/res/values/styles.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <style name="PickerDialogTheme" parent="@*android:style/Theme.DeviceDefault.Settings.Dialog">
+    </style>
+
+</resources>
diff --git a/packages/SoundPicker/src/com/android/soundpicker/BasePickerFragment.java b/packages/SoundPicker2/src/com/android/soundpicker/BasePickerFragment.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/BasePickerFragment.java
rename to packages/SoundPicker2/src/com/android/soundpicker/BasePickerFragment.java
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/CheckedListItem.java b/packages/SoundPicker2/src/com/android/soundpicker/CheckedListItem.java
new file mode 100644
index 0000000..819ae98
--- /dev/null
+++ b/packages/SoundPicker2/src/com/android/soundpicker/CheckedListItem.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.soundpicker;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.Checkable;
+import android.widget.CheckedTextView;
+import android.widget.RelativeLayout;
+
+/**
+ * The {@link CheckedListItem} is a layout item that represents a ringtone, and is used in
+ * {@link RingtonePickerActivity}. It contains the ringtone's name, and a work badge to right of the
+ * name if the ringtone belongs to a work profile.
+ */
+public class CheckedListItem extends RelativeLayout implements Checkable {
+
+    public CheckedListItem(Context context) {
+        super(context);
+    }
+
+    public CheckedListItem(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public CheckedListItem(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public CheckedListItem(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    public void setChecked(boolean checked) {
+        getCheckedTextView().setChecked(checked);
+    }
+
+    @Override
+    public boolean isChecked() {
+        return getCheckedTextView().isChecked();
+    }
+
+    @Override
+    public void toggle() {
+        getCheckedTextView().toggle();
+    }
+
+    private CheckedTextView getCheckedTextView() {
+        return (CheckedTextView) findViewById(R.id.checked_text_view);
+    }
+
+}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/ListeningExecutorServiceFactory.java b/packages/SoundPicker2/src/com/android/soundpicker/ListeningExecutorServiceFactory.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/ListeningExecutorServiceFactory.java
rename to packages/SoundPicker2/src/com/android/soundpicker/ListeningExecutorServiceFactory.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/LocalizedCursor.java b/packages/SoundPicker2/src/com/android/soundpicker/LocalizedCursor.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/LocalizedCursor.java
rename to packages/SoundPicker2/src/com/android/soundpicker/LocalizedCursor.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneFactory.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneFactory.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/RingtoneFactory.java
rename to packages/SoundPicker2/src/com/android/soundpicker/RingtoneFactory.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneListHandler.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneListHandler.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/RingtoneListHandler.java
rename to packages/SoundPicker2/src/com/android/soundpicker/RingtoneListHandler.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneListViewAdapter.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneListViewAdapter.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/RingtoneListViewAdapter.java
rename to packages/SoundPicker2/src/com/android/soundpicker/RingtoneListViewAdapter.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneManagerFactory.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneManagerFactory.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/RingtoneManagerFactory.java
rename to packages/SoundPicker2/src/com/android/soundpicker/RingtoneManagerFactory.java
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneOverlayService.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneOverlayService.java
new file mode 100644
index 0000000..b94ebeb
--- /dev/null
+++ b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneOverlayService.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.soundpicker;
+
+import android.app.Service;
+import android.content.Intent;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.IBinder;
+import android.provider.MediaStore;
+import android.provider.Settings.System;
+import android.util.Log;
+
+import androidx.annotation.IdRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Service to copy and set customization of default sounds
+ */
+public class RingtoneOverlayService extends Service {
+    private static final String TAG = "RingtoneOverlayService";
+    private static final boolean DEBUG = false;
+
+    @Override
+    public int onStartCommand(@Nullable final Intent intent, final int flags, final int startId) {
+        AsyncTask.execute(() -> {
+            updateRingtones();
+            stopSelf();
+        });
+
+        // Try again later if we are killed before we finish.
+        return Service.START_REDELIVER_INTENT;
+    }
+
+    @Override
+    public IBinder onBind(@Nullable final Intent intent) {
+        return null;
+    }
+
+    private void updateRingtones() {
+        copyResourceAndSetAsSound(R.raw.default_ringtone,
+                System.RINGTONE, Environment.DIRECTORY_RINGTONES);
+        copyResourceAndSetAsSound(R.raw.default_notification_sound,
+                System.NOTIFICATION_SOUND, Environment.DIRECTORY_NOTIFICATIONS);
+        copyResourceAndSetAsSound(R.raw.default_alarm_alert,
+                System.ALARM_ALERT, Environment.DIRECTORY_ALARMS);
+    }
+
+    /* If the resource contains any data, copy a resource to the file system, scan it, and set the
+     * file URI as the default for a sound. */
+    private void copyResourceAndSetAsSound(@IdRes final int id, @NonNull final String name,
+            @NonNull final String subPath) {
+        final File destDir = Environment.getExternalStoragePublicDirectory(subPath);
+        if (!destDir.exists() && !destDir.mkdirs()) {
+            Log.e(TAG, "can't create " + destDir.getAbsolutePath());
+            return;
+        }
+
+        final File dest = new File(destDir, "default_" + name + ".ogg");
+        try (
+                InputStream is = getResources().openRawResource(id);
+                FileOutputStream os = new FileOutputStream(dest);
+        ) {
+            if (is.available() > 0) {
+                FileUtils.copy(is, os);
+                final Uri uri = scanFile(dest);
+                if (uri != null) {
+                    set(name, uri);
+                }
+            } else {
+                // TODO Shall we remove any former copied resource in this case and unset
+                // the defaults if we use this event a second time to clear the data?
+                if (DEBUG) Log.d(TAG, "Resource for " + name + " has no overlay");
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "Unable to open resource for " + name + ": " + e);
+        }
+    }
+
+    private Uri scanFile(@NonNull final File file) {
+        return MediaStore.scanFile(getContentResolver(), file);
+    }
+
+    private void set(@NonNull final String name, @NonNull final Uri uri) {
+        final Uri settingUri = System.getUriFor(name);
+        RingtoneManager.setActualDefaultRingtoneUri(this,
+                RingtoneManager.getDefaultType(settingUri), uri);
+        System.putInt(getContentResolver(), name + "_set", 1);
+    }
+}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerActivity.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerActivity.java
new file mode 100644
index 0000000..90a14f9
--- /dev/null
+++ b/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerActivity.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2007 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.soundpicker;
+
+import android.content.Intent;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.util.Log;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentTransaction;
+import androidx.lifecycle.ViewModelProvider;
+
+import dagger.hilt.android.AndroidEntryPoint;
+
+/**
+ * The {@link RingtonePickerActivity} allows the user to choose one from all of the
+ * available ringtones. The chosen ringtone's URI will be persisted as a string.
+ *
+ * @see RingtoneManager#ACTION_RINGTONE_PICKER
+ */
+@AndroidEntryPoint(AppCompatActivity.class)
+public final class RingtonePickerActivity extends Hilt_RingtonePickerActivity {
+
+    private static final String TAG = "RingtonePickerActivity";
+    // TODO: Use the extra keys from RingtoneManager once they're added.
+    private static final String EXTRA_RINGTONE_PICKER_CATEGORY = "EXTRA_RINGTONE_PICKER_CATEGORY";
+    private static final String EXTRA_VIBRATION_SHOW_DEFAULT = "EXTRA_VIBRATION_SHOW_DEFAULT";
+    private static final String EXTRA_VIBRATION_DEFAULT_URI = "EXTRA_VIBRATION_DEFAULT_URI";
+    private static final String EXTRA_VIBRATION_SHOW_SILENT = "EXTRA_VIBRATION_SHOW_SILENT";
+    private static final String EXTRA_VIBRATION_EXISTING_URI = "EXTRA_VIBRATION_EXISTING_URI";
+    private static final boolean RINGTONE_PICKER_CATEGORY_FEATURE_ENABLED = false;
+
+    private RingtonePickerViewModel mRingtonePickerViewModel;
+    private int mAttributesFlags;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_ringtone_picker);
+
+        mRingtonePickerViewModel = new ViewModelProvider(this).get(RingtonePickerViewModel.class);
+
+        Intent intent = getIntent();
+        /**
+         * Id of the user to which the ringtone picker should list the ringtones
+         */
+        int pickerUserId = UserHandle.myUserId();
+
+        // Get the types of ringtones to show
+        int ringtoneType = intent.getIntExtra(RingtoneManager.EXTRA_RINGTONE_TYPE,
+                RingtonePickerViewModel.RINGTONE_TYPE_UNKNOWN);
+
+        // AudioAttributes flags
+        mAttributesFlags |= intent.getIntExtra(
+                RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS,
+                0 /*defaultValue == no flags*/);
+
+        boolean showOkCancelButtons = getResources().getBoolean(R.bool.config_showOkCancelButtons);
+
+        String title = intent.getStringExtra(RingtoneManager.EXTRA_RINGTONE_TITLE);
+        if (title == null) {
+            title = getString(RingtonePickerViewModel.getTitleByType(ringtoneType));
+        }
+        String ringtonePickerCategory = intent.getStringExtra(EXTRA_RINGTONE_PICKER_CATEGORY);
+        RingtonePickerViewModel.PickerType pickerType = mapCategoryToPickerType(
+                ringtonePickerCategory);
+
+        RingtoneListHandler.Config soundListConfig = getSoundListConfig(pickerType, intent,
+                ringtoneType);
+        RingtoneListHandler.Config vibrationListConfig = getVibrationListConfig(pickerType, intent);
+
+        RingtonePickerViewModel.Config pickerConfig =
+                new RingtonePickerViewModel.Config(title, pickerUserId, ringtoneType,
+                        showOkCancelButtons, mAttributesFlags, pickerType);
+
+        mRingtonePickerViewModel.init(pickerConfig, soundListConfig, vibrationListConfig);
+
+        if (savedInstanceState == null) {
+            TabbedDialogFragment dialogFragment = new TabbedDialogFragment();
+
+            FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
+            Fragment prev = getSupportFragmentManager().findFragmentByTag(TabbedDialogFragment.TAG);
+            if (prev != null) {
+                ft.remove(prev);
+            }
+            ft.addToBackStack(null);
+            dialogFragment.show(ft, TabbedDialogFragment.TAG);
+        }
+
+        // The volume keys will control the stream that we are choosing a ringtone for
+        setVolumeControlStream(mRingtonePickerViewModel.getRingtoneStreamType());
+    }
+
+    private RingtoneListHandler.Config getSoundListConfig(
+            RingtonePickerViewModel.PickerType pickerType, Intent intent, int ringtoneType) {
+        if (pickerType != RingtonePickerViewModel.PickerType.SOUND_PICKER
+                && pickerType != RingtonePickerViewModel.PickerType.RINGTONE_PICKER) {
+            // This ringtone picker does not require a sound picker.
+            return null;
+        }
+
+        // Get whether to show the 'Default' sound item, and the URI to play when it's clicked
+        boolean hasDefaultSoundItem =
+                intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
+
+        // The Uri to play when the 'Default' sound item is clicked.
+        Uri uriForDefaultSoundItem =
+                intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI);
+        if (uriForDefaultSoundItem == null) {
+            uriForDefaultSoundItem = RingtonePickerViewModel.getDefaultItemUriByType(ringtoneType);
+        }
+
+        // Get whether this list has the 'Silent' sound item.
+        boolean hasSilentSoundItem =
+                intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
+
+        // AudioAttributes flags
+        mAttributesFlags |= intent.getIntExtra(
+                RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS,
+                0 /*defaultValue == no flags*/);
+
+        // Get the sound URI whose list item should have a checkmark
+        Uri existingSoundUri = intent
+                .getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI);
+
+        return new RingtoneListHandler.Config(hasDefaultSoundItem,
+                uriForDefaultSoundItem, hasSilentSoundItem, existingSoundUri);
+    }
+
+    private RingtoneListHandler.Config getVibrationListConfig(
+            RingtonePickerViewModel.PickerType pickerType, Intent intent) {
+        if (pickerType != RingtonePickerViewModel.PickerType.VIBRATION_PICKER
+                && pickerType != RingtonePickerViewModel.PickerType.RINGTONE_PICKER) {
+            // This ringtone picker does not require a vibration picker.
+            return null;
+        }
+
+        // Get whether to show the 'Default' vibration item, and the URI to play when it's clicked
+        boolean hasDefaultVibrationItem =
+                intent.getBooleanExtra(EXTRA_VIBRATION_SHOW_DEFAULT, false);
+
+        // The Uri to play when the 'Default' vibration item is clicked.
+        Uri uriForDefaultVibrationItem = intent.getParcelableExtra(EXTRA_VIBRATION_DEFAULT_URI);
+
+        // Get whether this list has the 'Silent' vibration item.
+        boolean hasSilentVibrationItem =
+                intent.getBooleanExtra(EXTRA_VIBRATION_SHOW_SILENT, true);
+
+        // Get the vibration URI whose list item should have a checkmark
+        Uri existingVibrationUri = intent.getParcelableExtra(EXTRA_VIBRATION_EXISTING_URI);
+
+        return new RingtoneListHandler.Config(
+                hasDefaultVibrationItem, uriForDefaultVibrationItem, hasSilentVibrationItem,
+                existingVibrationUri);
+    }
+
+    @Override
+    public void onDestroy() {
+        mRingtonePickerViewModel.cancelPendingAsyncTasks();
+        super.onDestroy();
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        mRingtonePickerViewModel.onStop(isChangingConfigurations());
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mRingtonePickerViewModel.onPause(isChangingConfigurations());
+    }
+
+    /**
+     * Maps the ringtone picker category to the appropriate PickerType.
+     * If the category is null or the feature is still not released, then it defaults to sound
+     * picker.
+     *
+     * @param category the ringtone picker category.
+     * @return the corresponding picker type.
+     */
+    private static RingtonePickerViewModel.PickerType mapCategoryToPickerType(String category) {
+        if (category == null || !RINGTONE_PICKER_CATEGORY_FEATURE_ENABLED) {
+            return RingtonePickerViewModel.PickerType.SOUND_PICKER;
+        }
+
+        switch (category) {
+            case "android.intent.category.RINGTONE_PICKER_RINGTONE":
+                return RingtonePickerViewModel.PickerType.RINGTONE_PICKER;
+            case "android.intent.category.RINGTONE_PICKER_SOUND":
+                return RingtonePickerViewModel.PickerType.SOUND_PICKER;
+            case "android.intent.category.RINGTONE_PICKER_VIBRATION":
+                return RingtonePickerViewModel.PickerType.VIBRATION_PICKER;
+            default:
+                Log.w(TAG, "Unrecognized category: " + category + ". Defaulting to sound picker.");
+                return RingtonePickerViewModel.PickerType.SOUND_PICKER;
+        }
+    }
+}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerApplication.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerApplication.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/RingtonePickerApplication.java
rename to packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerApplication.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerViewModel.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerViewModel.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/RingtonePickerViewModel.java
rename to packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerViewModel.java
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneReceiver.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneReceiver.java
new file mode 100644
index 0000000..6a34936
--- /dev/null
+++ b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneReceiver.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2007 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.soundpicker;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class RingtoneReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        final String action = intent.getAction();
+        if (Intent.ACTION_DEVICE_CUSTOMIZATION_READY.equals(action)) {
+            initResourceRingtones(context);
+        }
+    }
+
+    private void initResourceRingtones(Context context) {
+        context.startService(
+                new Intent(context, RingtoneOverlayService.class));
+    }
+}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/SoundPickerFragment.java b/packages/SoundPicker2/src/com/android/soundpicker/SoundPickerFragment.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/SoundPickerFragment.java
rename to packages/SoundPicker2/src/com/android/soundpicker/SoundPickerFragment.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/TabbedDialogFragment.java b/packages/SoundPicker2/src/com/android/soundpicker/TabbedDialogFragment.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/TabbedDialogFragment.java
rename to packages/SoundPicker2/src/com/android/soundpicker/TabbedDialogFragment.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/VibrationPickerFragment.java b/packages/SoundPicker2/src/com/android/soundpicker/VibrationPickerFragment.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/VibrationPickerFragment.java
rename to packages/SoundPicker2/src/com/android/soundpicker/VibrationPickerFragment.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/ViewPagerAdapter.java b/packages/SoundPicker2/src/com/android/soundpicker/ViewPagerAdapter.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/ViewPagerAdapter.java
rename to packages/SoundPicker2/src/com/android/soundpicker/ViewPagerAdapter.java
diff --git a/packages/SoundPicker/tests/Android.bp b/packages/SoundPicker2/tests/Android.bp
similarity index 94%
rename from packages/SoundPicker/tests/Android.bp
rename to packages/SoundPicker2/tests/Android.bp
index c38426f..d88d442 100644
--- a/packages/SoundPicker/tests/Android.bp
+++ b/packages/SoundPicker2/tests/Android.bp
@@ -17,7 +17,7 @@
 }
 
 android_test {
-    name: "SoundPickerTests",
+    name: "SoundPicker2Tests",
     certificate: "platform",
     libs: [
         "android.test.runner",
@@ -30,7 +30,7 @@
         "androidx.test.ext.truth",
         "mockito-target-minus-junit4",
         "guava-android-testlib",
-        "SoundPickerLib",
+        "SoundPicker2Lib",
     ],
     srcs: [
         "src/**/*.java",
diff --git a/packages/SoundPicker/tests/AndroidManifest.xml b/packages/SoundPicker2/tests/AndroidManifest.xml
similarity index 100%
rename from packages/SoundPicker/tests/AndroidManifest.xml
rename to packages/SoundPicker2/tests/AndroidManifest.xml
diff --git a/packages/SoundPicker/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java b/packages/SoundPicker2/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java
similarity index 100%
rename from packages/SoundPicker/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java
rename to packages/SoundPicker2/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java
diff --git a/packages/SoundPicker/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java b/packages/SoundPicker2/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java
similarity index 100%
rename from packages/SoundPicker/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java
rename to packages/SoundPicker2/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 03f7c99..de73b77 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -32,22 +32,6 @@
       ]
     },
     {
-      "name": "SystemUIGoogleScreenshotTests",
-      "options": [
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "android.platform.test.annotations.Postsubmit"
-        }
-      ],
-      // The test doesn't run on AOSP Cuttlefish
-      "keywords": ["internal"]
-    },
-    {
       // TODO(b/251476085): Consider merging with SystemUIGoogleScreenshotTests (in U+)
       "name": "SystemUIGoogleBiometricsScreenshotTests",
       "options": [
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
index 0f55f35..eadcd7c 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
@@ -1,15 +1,17 @@
 package: "com.android.systemui.accessibility.accessibilitymenu"
 
-flag {
-    name: "a11y_menu_settings_back_button_fix_and_large_button_sizing"
-    namespace: "accessibility"
-    description: "Provides/restores back button functionality for the a11yMenu settings page. Also, fixes sizing problems with large shortcut buttons."
-    bug: "298467628"
-}
+# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
 
 flag {
     name: "a11y_menu_hide_before_taking_action"
     namespace: "accessibility"
     description: "Hides the AccessibilityMenuService UI before taking action instead of after."
     bug: "292020123"
-}
\ No newline at end of file
+}
+
+flag {
+    name: "a11y_menu_settings_back_button_fix_and_large_button_sizing"
+    namespace: "accessibility"
+    description: "Provides/restores back button functionality for the a11yMenu settings page. Also, fixes sizing problems with large shortcut buttons."
+    bug: "298467628"
+}
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig
index 8841967..bcf1535 100644
--- a/packages/SystemUI/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/aconfig/accessibility.aconfig
@@ -1,5 +1,7 @@
 package: "com.android.systemui"
 
+# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
+
 flag {
     name: "floating_menu_overlaps_nav_bars_flag"
     namespace: "accessibility"
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 2509cfd..0567528 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -29,3 +29,17 @@
         "Notification Manager Service"
     bug: "299448097"
 }
+
+flag {
+    name: "notification_async_hybrid_view_inflation"
+    namespace: "systemui"
+    description: "Inflates the hybrid (single-line) notification views form the background thread."
+    bug: "217799515"
+}
+
+flag {
+    name: "scene_container"
+    namespace: "systemui"
+    description: "Enables the scene container framework go/flexiglass."
+    bug: "283121968"
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index 6c4b695..af35ea4 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -68,6 +68,7 @@
     override var launchContainer = ghostedView.rootView as ViewGroup
     private val launchContainerOverlay: ViewGroupOverlay
         get() = launchContainer.overlay
+
     private val launchContainerLocation = IntArray(2)
 
     /** The ghost view that is drawn and animated instead of the ghosted view. */
@@ -206,9 +207,8 @@
             return
         }
 
-        backgroundView = FrameLayout(launchContainer.context).also {
-            launchContainerOverlay.add(it)
-        }
+        backgroundView =
+            FrameLayout(launchContainer.context).also { launchContainerOverlay.add(it) }
 
         // We wrap the ghosted view background and use it to draw the expandable background. Its
         // alpha will be set to 0 as soon as we start drawing the expanding background.
@@ -226,6 +226,17 @@
         // the content before fading out the background.
         ghostView = GhostView.addGhost(ghostedView, launchContainer)
 
+        // [GhostView.addGhost], the result of which is our [ghostView], creates a [GhostView], and
+        // adds it first to a [FrameLayout] container. It then adds _that_ container to an
+        // [OverlayViewGroup]. We need to turn off clipping for that container view. Currently,
+        // however, the only way to get a reference to that overlay is by going through our
+        // [ghostView]. The [OverlayViewGroup] will always be its grandparent view.
+        // TODO(b/306652954) reference the overlay view group directly if we can
+        (ghostView?.parent?.parent as? ViewGroup)?.let {
+            it.clipChildren = false
+            it.clipToPadding = false
+        }
+
         val matrix = ghostView?.animationMatrix ?: Matrix.IDENTITY_MATRIX
         matrix.getValues(initialGhostViewMatrixValues)
 
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt
index 4ed78b3..33024f7 100644
--- a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt
+++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.communal.layout.ui.compose
 
+import android.util.SizeF
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
@@ -54,7 +55,14 @@
                         Row(
                             modifier = Modifier.height(layoutConfig.cardHeight(cardInfo.size)),
                         ) {
-                            cardInfo.card.Content(Modifier.fillMaxSize())
+                            cardInfo.card.Content(
+                                modifier = Modifier.fillMaxSize(),
+                                size =
+                                    SizeF(
+                                        layoutConfig.cardWidth.value,
+                                        layoutConfig.cardHeight(cardInfo.size).value,
+                                    ),
+                            )
                         }
                     }
                 }
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt
index ac8aa67..4b2a156 100644
--- a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt
+++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.communal.layout.ui.compose.config
 
+import android.util.SizeF
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 
@@ -26,8 +27,11 @@
      *
      * To host non-Compose views, see
      * https://developer.android.com/jetpack/compose/migrate/interoperability-apis/views-in-compose.
+     *
+     * @param size The size given to the card. Content of the card should fill all this space, given
+     *   that margins and paddings have been taken care of by the layout.
      */
-    @Composable abstract fun Content(modifier: Modifier)
+    @Composable abstract fun Content(modifier: Modifier, size: SizeF)
 
     /**
      * Sizes supported by the card.
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 fdf65f5..c1974ca 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
@@ -1,5 +1,6 @@
 package com.android.systemui.communal.layout
 
+import android.util.SizeF
 import androidx.compose.material3.Card
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
@@ -91,7 +92,7 @@
             override val supportedSizes = listOf(size)
 
             @Composable
-            override fun Content(modifier: Modifier) {
+            override fun Content(modifier: Modifier, size: SizeF) {
                 Card(modifier = modifier, content = {})
             }
         }
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
index c6e429a..ddd1c67 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -72,6 +72,10 @@
         throwComposeUnavailableError()
     }
 
+    override fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View {
+        throwComposeUnavailableError()
+    }
+
     private fun throwComposeUnavailableError(): Nothing {
         error(
             "Compose is not available. Make sure to check isComposeAvailable() before calling any" +
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index 1722685..eeda6c6 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
 import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout
 import com.android.systemui.common.ui.compose.windowinsets.DisplayCutoutProvider
+import com.android.systemui.communal.ui.compose.CommunalContainer
 import com.android.systemui.communal.ui.compose.CommunalHub
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.people.ui.compose.PeopleScreen
@@ -104,6 +105,12 @@
         }
     }
 
+    override fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View {
+        return ComposeView(context).apply {
+            setContent { PlatformTheme { CommunalContainer(viewModel = viewModel) } }
+        }
+    }
+
     // TODO(b/298525212): remove once Compose exposes window inset bounds.
     private fun displayCutoutFromWindowInsets(
         scope: CoroutineScope,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
new file mode 100644
index 0000000..46d418a
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -0,0 +1,123 @@
+package com.android.systemui.communal.ui.compose
+
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.width
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.SceneTransitionLayout
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.transitions
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+
+object Scenes {
+    val Blank = SceneKey(name = "blank")
+    val Communal = SceneKey(name = "communal")
+}
+
+object Communal {
+    object Elements {
+        val Content = ElementKey("CommunalContent")
+    }
+}
+
+val sceneTransitions = transitions {
+    from(Scenes.Blank, to = Scenes.Communal) {
+        spec = tween(durationMillis = 500)
+
+        translate(Communal.Elements.Content, Edge.Right)
+        fade(Communal.Elements.Content)
+    }
+}
+
+/**
+ * View containing a [SceneTransitionLayout] that shows the communal UI and handles transitions.
+ *
+ * This is a temporary container to allow the communal UI to use [SceneTransitionLayout] for gesture
+ * handling and transitions before the full Flexiglass layout is ready.
+ */
+@Composable
+fun CommunalContainer(modifier: Modifier = Modifier, viewModel: CommunalViewModel) {
+    val (currentScene, setCurrentScene) = remember { mutableStateOf(Scenes.Blank) }
+
+    // Failsafe to hide the whole SceneTransitionLayout in case of bugginess.
+    var showSceneTransitionLayout by remember { mutableStateOf(true) }
+    if (!showSceneTransitionLayout) {
+        return
+    }
+
+    SceneTransitionLayout(
+        modifier = modifier.fillMaxSize(),
+        currentScene = currentScene,
+        onChangeScene = setCurrentScene,
+        transitions = sceneTransitions,
+    ) {
+        scene(Scenes.Blank, userActions = mapOf(Swipe.Left to Scenes.Communal)) {
+            BlankScene { showSceneTransitionLayout = false }
+        }
+
+        scene(
+            Scenes.Communal,
+            userActions = mapOf(Swipe.Right to Scenes.Blank),
+        ) {
+            CommunalScene(viewModel, modifier = modifier)
+        }
+    }
+}
+
+/**
+ * Blank scene that shows over keyguard/dream. This scene will eventually show nothing at all and is
+ * only used to allow for transitions to the communal scene.
+ */
+@Composable
+private fun BlankScene(
+    modifier: Modifier = Modifier,
+    hideSceneTransitionLayout: () -> Unit,
+) {
+    Box(modifier.fillMaxSize()) {
+        Column(
+            Modifier.fillMaxHeight()
+                .width(100.dp)
+                .align(Alignment.CenterEnd)
+                .background(Color(0x55e9f2eb)),
+            verticalArrangement = Arrangement.Center,
+            horizontalAlignment = Alignment.CenterHorizontally
+        ) {
+            Text("Default scene")
+
+            IconButton(onClick = hideSceneTransitionLayout) {
+                Icon(Icons.Filled.Close, contentDescription = "Close button")
+            }
+        }
+    }
+}
+
+/** Scene containing the glanceable hub UI. */
+@Composable
+private fun SceneScope.CommunalScene(
+    viewModel: CommunalViewModel,
+    modifier: Modifier = Modifier,
+) {
+    Box(modifier.element(Communal.Elements.Content)) { CommunalHub(viewModel = viewModel) }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 3d827fb..b8fb264 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -1,5 +1,8 @@
 package com.android.systemui.communal.ui.compose
 
+import android.appwidget.AppWidgetHostView
+import android.os.Bundle
+import android.util.SizeF
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
@@ -12,9 +15,12 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.res.integerResource
+import androidx.compose.ui.viewinterop.AndroidView
 import com.android.systemui.communal.layout.ui.compose.CommunalGridLayout
 import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard
 import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutConfig
+import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.ui.model.CommunalContentUiModel
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.res.R
 
@@ -24,6 +30,7 @@
     viewModel: CommunalViewModel,
 ) {
     val showTutorial by viewModel.showTutorialContent.collectAsState(initial = false)
+    val widgetContent by viewModel.widgetContent.collectAsState(initial = emptyList())
     Box(
         modifier = modifier.fillMaxSize().background(Color.White),
     ) {
@@ -36,7 +43,7 @@
                     gridHeight = dimensionResource(R.dimen.communal_grid_height),
                     gridColumnsPerCard = integerResource(R.integer.communal_grid_columns_per_card),
                 ),
-            communalCards = if (showTutorial) tutorialContent else emptyList(),
+            communalCards = if (showTutorial) tutorialContent else widgetContent.map(::contentCard),
         )
     }
 }
@@ -58,8 +65,37 @@
         override val supportedSizes = listOf(size)
 
         @Composable
-        override fun Content(modifier: Modifier) {
+        override fun Content(modifier: Modifier, size: SizeF) {
             Card(modifier = modifier, content = {})
         }
     }
 }
+
+private fun contentCard(model: CommunalContentUiModel): CommunalGridLayoutCard {
+    return object : CommunalGridLayoutCard() {
+        override val supportedSizes = listOf(convertToCardSize(model.size))
+        override val priority = model.priority
+
+        @Composable
+        override fun Content(modifier: Modifier, size: SizeF) {
+            AndroidView(
+                modifier = modifier,
+                factory = {
+                    model.view.apply {
+                        if (this is AppWidgetHostView) {
+                            updateAppWidgetSize(Bundle(), listOf(size))
+                        }
+                    }
+                },
+            )
+        }
+    }
+}
+
+private fun convertToCardSize(size: CommunalContentSize): CommunalGridLayoutCard.Size {
+    return when (size) {
+        CommunalContentSize.FULL -> CommunalGridLayoutCard.Size.FULL
+        CommunalContentSize.HALF -> CommunalGridLayoutCard.Size.HALF
+        CommunalContentSize.THIRD -> CommunalGridLayoutCard.Size.THIRD
+    }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index ce96bbf..abc62c4 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -43,6 +43,7 @@
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.round
 import com.android.compose.animation.scene.transformation.PropertyTransformation
+import com.android.compose.animation.scene.transformation.SharedElementTransformation
 import com.android.compose.modifiers.thenIf
 import com.android.compose.ui.util.lerp
 
@@ -196,29 +197,44 @@
             state.fromScene == state.toScene ||
             !layoutImpl.isTransitionReady(state) ||
             state.fromScene !in element.sceneValues ||
-            state.toScene !in element.sceneValues ||
-            !isSharedElementEnabled(layoutImpl, state, element.key)
+            state.toScene !in element.sceneValues
     ) {
         return true
     }
 
-    val otherScene =
-        layoutImpl.scenes.getValue(
-            if (scene.key == state.fromScene) {
-                state.toScene
-            } else {
-                state.fromScene
-            }
-        )
-
-    // When the element is shared, draw the one in the highest scene unless it is a background, i.e.
-    // it is usually drawn below everything else.
-    val isHighestScene = scene.zIndex > otherScene.zIndex
-    return if (element.key.isBackground) {
-        !isHighestScene
-    } else {
-        isHighestScene
+    val sharedTransformation = sharedElementTransformation(layoutImpl, state, element.key)
+    if (sharedTransformation?.enabled == false) {
+        return true
     }
+
+    return shouldDrawOrComposeSharedElement(
+        layoutImpl,
+        state,
+        scene.key,
+        element.key,
+        sharedTransformation,
+    )
+}
+
+internal fun shouldDrawOrComposeSharedElement(
+    layoutImpl: SceneTransitionLayoutImpl,
+    transition: TransitionState.Transition,
+    scene: SceneKey,
+    element: ElementKey,
+    sharedTransformation: SharedElementTransformation?
+): Boolean {
+    val scenePicker = sharedTransformation?.scenePicker ?: DefaultSharedElementScenePicker
+    val fromScene = transition.fromScene
+    val toScene = transition.toScene
+
+    return scenePicker.sceneDuringTransition(
+        element = element,
+        fromScene = fromScene,
+        toScene = toScene,
+        progress = transition::progress,
+        fromSceneZIndex = layoutImpl.scenes.getValue(fromScene).zIndex,
+        toSceneZIndex = layoutImpl.scenes.getValue(toScene).zIndex,
+    ) == scene
 }
 
 private fun isSharedElementEnabled(
@@ -226,6 +242,14 @@
     transition: TransitionState.Transition,
     element: ElementKey,
 ): Boolean {
+    return sharedElementTransformation(layoutImpl, transition, element)?.enabled ?: true
+}
+
+internal fun sharedElementTransformation(
+    layoutImpl: SceneTransitionLayoutImpl,
+    transition: TransitionState.Transition,
+    element: ElementKey,
+): SharedElementTransformation? {
     val spec = layoutImpl.transitions.transitionSpec(transition.fromScene, transition.toScene)
     val sharedInFromScene = spec.transformations(element, transition.fromScene).shared
     val sharedInToScene = spec.transformations(element, transition.toScene).shared
@@ -238,7 +262,7 @@
         )
     }
 
-    return sharedInFromScene?.enabled ?: true
+    return sharedInFromScene
 }
 
 /**
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
index 6dbeb69..fa385d0 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
@@ -22,6 +22,8 @@
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.ui.Modifier
@@ -60,7 +62,16 @@
         // which case we still need to draw it.
         val picture = remember { Picture() }
 
-        if (shouldComposeMovableElement(layoutImpl, scene.key, element)) {
+        // Whether we should compose the movable element here. The scene picker logic to know in
+        // which scene we should compose/draw a movable element might depend on the current
+        // transition progress, so we put this in a derivedStateOf to prevent many recompositions
+        // during the transition.
+        val shouldComposeMovableElement by
+            remember(layoutImpl, scene.key, element) {
+                derivedStateOf { shouldComposeMovableElement(layoutImpl, scene.key, element) }
+            }
+
+        if (shouldComposeMovableElement) {
             Box(
                 Modifier.drawWithCache {
                     val width = size.width.toInt()
@@ -172,14 +183,13 @@
         return scene == fromScene
     }
 
-    // If we are ready in both scenes, then compose in the scene that has the highest zIndex (unless
-    // it is a background) given that this is the one that is going to be drawn.
-    val isHighestScene = layoutImpl.scene(scene).zIndex > layoutImpl.scene(otherScene).zIndex
-    return if (element.key.isBackground) {
-        !isHighestScene
-    } else {
-        isHighestScene
-    }
+    return shouldDrawOrComposeSharedElement(
+        layoutImpl,
+        transitionState,
+        scene,
+        element.key,
+        sharedElementTransformation(layoutImpl, transitionState, element.key),
+    )
 }
 
 private class MovableElementScopeImpl(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index 4966977..7b7ddfa 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -120,8 +120,14 @@
      *
      * @param enabled whether the matched element(s) should actually be shared in this transition.
      *   Defaults to true.
+     * @param scenePicker the [SharedElementScenePicker] to use when deciding in which scene we
+     *   should draw or compose this shared element.
      */
-    fun sharedElement(matcher: ElementMatcher, enabled: Boolean = true)
+    fun sharedElement(
+        matcher: ElementMatcher,
+        enabled: Boolean = true,
+        scenePicker: SharedElementScenePicker = DefaultSharedElementScenePicker,
+    )
 
     /**
      * Punch a hole in the element(s) matching [matcher] that has the same bounds as [bounds] and
@@ -144,6 +150,44 @@
     fun reversed(builder: TransitionBuilder.() -> Unit)
 }
 
+interface SharedElementScenePicker {
+    /**
+     * Return the scene in which [element] should be drawn (when using `Modifier.element(key)`) or
+     * composed (when using `MovableElement(key)`) during the transition from [fromScene] to
+     * [toScene].
+     */
+    fun sceneDuringTransition(
+        element: ElementKey,
+        fromScene: SceneKey,
+        toScene: SceneKey,
+        progress: () -> Float,
+        fromSceneZIndex: Float,
+        toSceneZIndex: Float,
+    ): SceneKey
+}
+
+object DefaultSharedElementScenePicker : SharedElementScenePicker {
+    override fun sceneDuringTransition(
+        element: ElementKey,
+        fromScene: SceneKey,
+        toScene: SceneKey,
+        progress: () -> Float,
+        fromSceneZIndex: Float,
+        toSceneZIndex: Float
+    ): SceneKey {
+        // By default shared elements are drawn in the highest scene possible, unless it is a
+        // background.
+        return if (
+            (fromSceneZIndex > toSceneZIndex && !element.isBackground) ||
+                (fromSceneZIndex < toSceneZIndex && element.isBackground)
+        ) {
+            fromScene
+        } else {
+            toScene
+        }
+    }
+}
+
 @TransitionDsl
 interface PropertyTransformationBuilder {
     /**
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index f1c2717..d2bfd91 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -111,8 +111,12 @@
         range = null
     }
 
-    override fun sharedElement(matcher: ElementMatcher, enabled: Boolean) {
-        transformations.add(SharedElementTransformation(matcher, enabled))
+    override fun sharedElement(
+        matcher: ElementMatcher,
+        enabled: Boolean,
+        scenePicker: SharedElementScenePicker,
+    ) {
+        transformations.add(SharedElementTransformation(matcher, enabled, scenePicker))
     }
 
     override fun timestampRange(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
index 2ef8d56..0db8469 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
@@ -21,6 +21,7 @@
 import com.android.compose.animation.scene.ElementMatcher
 import com.android.compose.animation.scene.Scene
 import com.android.compose.animation.scene.SceneTransitionLayoutImpl
+import com.android.compose.animation.scene.SharedElementScenePicker
 import com.android.compose.animation.scene.TransitionState
 
 /** A transformation applied to one or more elements during a transition. */
@@ -48,6 +49,7 @@
 internal class SharedElementTransformation(
     override val matcher: ElementMatcher,
     internal val enabled: Boolean,
+    internal val scenePicker: SharedElementScenePicker,
 ) : Transformation
 
 /** A transformation that is applied on the element during the whole transition. */
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/grid/Grids.kt b/packages/SystemUI/compose/scene/src/com/android/compose/grid/Grids.kt
index 27f0948..790665a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/grid/Grids.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/grid/Grids.kt
@@ -116,9 +116,14 @@
 
         if (sizeCache.rowHeights.size != rows) {
             sizeCache.rowHeights = IntArray(rows) { 0 }
+        } else {
+            repeat(rows) { i -> sizeCache.rowHeights[i] = 0 }
         }
+
         if (sizeCache.columnWidths.size != columns) {
             sizeCache.columnWidths = IntArray(columns) { 0 }
+        } else {
+            repeat(columns) { i -> sizeCache.columnWidths[i] = 0 }
         }
 
         val totalHorizontalSpacingBetweenChildren =
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
index 4204cd5..83af630 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
@@ -144,7 +144,36 @@
         rule.testTransition(
             fromSceneContent = { MovableCounter(TestElements.Foo, Modifier.size(50.dp)) },
             toSceneContent = { MovableCounter(TestElements.Foo, Modifier.size(100.dp)) },
-            transition = { spec = tween(durationMillis = 16 * 4, easing = LinearEasing) },
+            transition = {
+                spec = tween(durationMillis = 16 * 4, easing = LinearEasing)
+                sharedElement(
+                    TestElements.Foo,
+                    scenePicker =
+                        object : SharedElementScenePicker {
+                            override fun sceneDuringTransition(
+                                element: ElementKey,
+                                fromScene: SceneKey,
+                                toScene: SceneKey,
+                                progress: () -> Float,
+                                fromSceneZIndex: Float,
+                                toSceneZIndex: Float
+                            ): SceneKey {
+                                assertThat(fromScene).isEqualTo(TestScenes.SceneA)
+                                assertThat(toScene).isEqualTo(TestScenes.SceneB)
+                                assertThat(fromSceneZIndex).isEqualTo(0)
+                                assertThat(toSceneZIndex).isEqualTo(1)
+
+                                // Compose Foo in Scene A if progress < 0.65f, otherwise compose it
+                                // in Scene B.
+                                return if (progress() < 0.65f) {
+                                    TestScenes.SceneA
+                                } else {
+                                    TestScenes.SceneB
+                                }
+                            }
+                        }
+                )
+            },
             fromScene = TestScenes.SceneA,
             toScene = TestScenes.SceneB,
         ) {
@@ -170,9 +199,12 @@
 
             at(32) {
                 // During the transition, there is a single counter that is moved, with the current
-                // value.
+                // value. Given that progress = 0.5f, it is currently composed in SceneA.
                 rule
-                    .onNode(hasText("count: 3"))
+                    .onNode(
+                        hasText("count: 3") and
+                            hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneA))
+                    )
                     .assertIsDisplayed()
                     .assertSizeIsEqualTo(75.dp, 75.dp)
 
@@ -186,6 +218,26 @@
                     .isEqualTo(1)
             }
 
+            at(48) {
+                // During the transition, there is a single counter that is moved, with the current
+                // value. Given that progress = 0.75f, it is currently composed in SceneB.
+                rule
+                    .onNode(
+                        hasText("count: 3") and
+                            hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneB))
+                    )
+                    .assertIsDisplayed()
+
+                // There are no other counters.
+                assertThat(
+                        rule
+                            .onAllNodesWithText("count: ", substring = true)
+                            .fetchSemanticsNodes()
+                            .size
+                    )
+                    .isEqualTo(1)
+            }
+
             after {
                 // At the end of the transition, the counter still has the current value.
                 rule
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index 5a70b79..452bc31 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -66,7 +66,7 @@
             <FrameLayout
                 android:id="@+id/status_bar_start_side_content"
                 android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
+                android:layout_height="match_parent"
                 android:layout_gravity="center_vertical|start"
                 android:clipChildren="false">
 
@@ -77,7 +77,7 @@
                      and DISABLE_NOTIFICATION_ICONS, respectively -->
                 <LinearLayout
                     android:id="@+id/status_bar_start_side_except_heads_up"
-                    android:layout_height="wrap_content"
+                    android:layout_height="match_parent"
                     android:layout_width="match_parent"
                     android:layout_gravity="center_vertical|start"
                     android:clipChildren="false">
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index acee425..7e03bd9 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -26,24 +26,6 @@
     android:layout_height="match_parent"
     android:fitsSystemWindows="true">
 
-    <com.android.systemui.statusbar.BackDropView
-        android:id="@+id/backdrop"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:visibility="gone"
-        sysui:ignoreRightInset="true"
-    >
-        <ImageView android:id="@+id/backdrop_back"
-                   android:layout_width="match_parent"
-                   android:scaleType="centerCrop"
-                   android:layout_height="match_parent" />
-        <ImageView android:id="@+id/backdrop_front"
-                   android:layout_width="match_parent"
-                   android:layout_height="match_parent"
-                   android:scaleType="centerCrop"
-                   android:visibility="invisible" />
-    </com.android.systemui.statusbar.BackDropView>
-
     <com.android.systemui.scrim.ScrimView
         android:id="@+id/scrim_behind"
         android:layout_width="match_parent"
@@ -63,7 +45,8 @@
     <com.android.systemui.statusbar.LightRevealScrim
         android:id="@+id/light_reveal_scrim"
         android:layout_width="match_parent"
-        android:layout_height="match_parent" />
+        android:layout_height="match_parent"
+        sysui:ignoreRightInset="true" />
 
     <include layout="@layout/status_bar_expanded"
              android:layout_width="match_parent"
@@ -83,6 +66,12 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
 
+    <!-- Placeholder for the communal UI that will be replaced if the feature is enabled. -->
+    <ViewStub
+        android:id="@+id/communal_ui_stub"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
     <include layout="@layout/brightness_mirror_container" />
 
     <com.android.systemui.scrim.ScrimView
diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml
index 587caaf..db526b1 100644
--- a/packages/SystemUI/res/values-land/config.xml
+++ b/packages/SystemUI/res/values-land/config.xml
@@ -46,4 +46,7 @@
      For now, this value has effect only when flag lockscreen.enable_landscape is enabled.
      TODO (b/293252410) - change this comment/resource when flag is enabled -->
     <bool name="force_config_use_split_notification_shade">true</bool>
+
+    <!-- Whether to show bottom sheets edge to edge -->
+    <bool name="config_edgeToEdgeBottomSheetDialog">false</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 9ac1e9f..572f6ff 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -531,19 +531,25 @@
     </string>
 
     <!-- A path similar to frameworks/base/core/res/res/values/config.xml
-      config_mainBuiltInDisplayCutout that describes a path larger than the exact path of a display
-      cutout. If present as well as config_enableDisplayCutoutProtection is set to true, then
-      SystemUI will draw this "protection path" instead of the display cutout path that is normally
-      used for anti-aliasing.
+      config_mainBuiltInDisplayCutout that describes a path larger than the exact path of a outer
+      display cutout. If present as well as config_enableDisplayCutoutProtection is set to true,
+      then SystemUI will draw this "protection path" instead of the display cutout path that is
+      normally used for anti-aliasing.
 
       This path will only be drawn when the front-facing camera turns on, otherwise the main
       DisplayCutout path will be rendered
        -->
     <string translatable="false" name="config_frontBuiltInDisplayCutoutProtection"></string>
 
-    <!--  ID for the camera that needs extra protection -->
+    <!--  ID for the camera of outer display that needs extra protection -->
     <string translatable="false" name="config_protectedCameraId"></string>
 
+    <!-- Similar to config_frontBuiltInDisplayCutoutProtection but for inner display. -->
+    <string translatable="false" name="config_innerBuiltInDisplayCutoutProtection"></string>
+
+    <!-- ID for the camera of inner display that needs extra protection -->
+    <string translatable="false" name="config_protectedInnerCameraId"></string>
+
     <!-- Comma-separated list of packages to exclude from camera protection e.g.
     "com.android.systemui,com.android.xyz" -->
     <string translatable="false" name="config_cameraProtectionExcludedPackages"></string>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 7a6d29a..9e2ebf6 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3224,6 +3224,9 @@
     <!--- Label of the dismiss button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]-->
     <string name="dismiss_dialog">Dismiss</string>
 
+    <!--- Content description of the connected display status bar icon that appears every time a display is connected [CHAR LIMIT=NONE]-->
+    <string name="connected_display_icon_desc">Display connected</string>
+
     <!-- Title of the privacy dialog, shown for active / recent app usage of some phone sensors [CHAR LIMIT=30] -->
     <string name="privacy_dialog_title">Microphone &amp; Camera</string>
     <!-- Subtitle of the privacy dialog, shown for active / recent app usage of some phone sensors [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt
index db46ccf..80f70a0 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt
@@ -33,6 +33,10 @@
     val hasFingerprint: Boolean
         get() = fingerprintProperties != null
 
+    /** If SFPS authentication is available. */
+    val hasSfps: Boolean
+        get() = hasFingerprint && fingerprintProperties!!.isAnySidefpsType
+
     /** If fingerprint authentication is available (and [faceProperties] is non-null). */
     val hasFace: Boolean
         get() = faceProperties != null
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
index 1e89614..400f652 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
@@ -702,13 +702,18 @@
 
         @Override
         public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) {
-            // Only hide the icon if the top task changes its requestedOrientation
-            // Launcher can alter its requestedOrientation while it's not on top, don't hide on this
-            Optional.ofNullable(ActivityManagerWrapper.getInstance())
-                    .map(ActivityManagerWrapper::getRunningTask)
-                    .ifPresent(a -> {
-                        if (a.id == taskId) setRotateSuggestionButtonState(false /* visible */);
-                    });
+            mBgExecutor.execute(() -> {
+                // Only hide the icon if the top task changes its requestedOrientation Launcher can
+                // alter its requestedOrientation while it's not on top, don't hide on this
+                Optional.ofNullable(ActivityManagerWrapper.getInstance())
+                        .map(ActivityManagerWrapper::getRunningTask)
+                        .ifPresent(a -> {
+                            if (a.id == taskId) {
+                                mMainThreadHandler.post(() ->
+                                        setRotateSuggestionButtonState(false /* visible */));
+                            }
+                        });
+            });
         }
     }
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
index eb20669..c505bd5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
@@ -61,6 +61,8 @@
             InteractionJankMonitor.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS;
     public static final int CUJ_OPEN_SEARCH_RESULT =
             InteractionJankMonitor.CUJ_LAUNCHER_OPEN_SEARCH_RESULT;
+    public static final int CUJ_LAUNCHER_UNFOLD_ANIM =
+            InteractionJankMonitor.CUJ_LAUNCHER_UNFOLD_ANIM;
 
     @IntDef({
             CUJ_APP_LAUNCH_FROM_RECENTS,
@@ -77,6 +79,7 @@
             CUJ_CLOSE_ALL_APPS_SWIPE,
             CUJ_CLOSE_ALL_APPS_TO_HOME,
             CUJ_OPEN_SEARCH_RESULT,
+            CUJ_LAUNCHER_UNFOLD_ANIM,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateRepository.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateRepository.kt
new file mode 100644
index 0000000..f219cec
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateRepository.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.unfold.system
+
+import com.android.systemui.unfold.dagger.UnfoldMain
+import com.android.systemui.unfold.updates.FoldProvider
+import com.android.systemui.unfold.updates.FoldProvider.FoldCallback
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import javax.inject.Singleton
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.buffer
+import kotlinx.coroutines.flow.callbackFlow
+
+/** Provides whether the device is folded. */
+interface DeviceStateRepository {
+    val isFolded: Flow<Boolean>
+}
+
+@Singleton
+class DeviceStateRepositoryImpl
+@Inject
+constructor(
+    private val foldProvider: FoldProvider,
+    @UnfoldMain private val executor: Executor,
+) : DeviceStateRepository {
+
+    override val isFolded: Flow<Boolean>
+        get() =
+            callbackFlow {
+                    val callback = FoldCallback { isFolded -> trySend(isFolded) }
+                    foldProvider.registerCallback(callback, executor)
+                    awaitClose { foldProvider.unregisterCallback(callback) }
+                }
+                .buffer(capacity = Channel.CONFLATED)
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
index fe607e1..7b67e3f 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
@@ -48,6 +48,9 @@
     abstract fun foldState(provider: DeviceStateManagerFoldProvider): FoldProvider
 
     @Binds
+    abstract fun deviceStateRepository(provider: DeviceStateRepositoryImpl): DeviceStateRepository
+
+    @Binds
     @UnfoldMain
     abstract fun mainExecutor(@Main executor: Executor): Executor
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/util/TraceStateLogger.kt b/packages/SystemUI/shared/src/com/android/systemui/util/TraceStateLogger.kt
new file mode 100644
index 0000000..63ea116
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/util/TraceStateLogger.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.util
+
+import android.os.Trace
+
+/**
+ * Utility class used to log state changes easily in a track with a custom name.
+ *
+ * Example of usage:
+ * ```kotlin
+ * class MyClass {
+ *    val screenStateLogger = TraceStateLogger("Screen state")
+ *
+ *    fun onTurnedOn() { screenStateLogger.log("on") }
+ *    fun onTurnedOff() { screenStateLogger.log("off") }
+ * }
+ * ```
+ *
+ * This creates a new slice in a perfetto trace only if the state is different than the previous
+ * one.
+ */
+class TraceStateLogger(
+    private val trackName: String,
+    private val logOnlyIfDifferent: Boolean = true,
+    private val instantEvent: Boolean = true
+) {
+
+    private var previousValue: String? = null
+
+    /** If needed, logs the value to a track with name [trackName]. */
+    fun log(newValue: String) {
+        if (instantEvent) {
+            Trace.instantForTrack(Trace.TRACE_TAG_APP, trackName, newValue)
+        }
+        if (logOnlyIfDifferent && previousValue == newValue) return
+        Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, trackName, 0)
+        Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, trackName, newValue, 0)
+        previousValue = newValue
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
index c4f1ce8..b186018 100644
--- a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
@@ -33,11 +33,11 @@
 import android.provider.Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS
 import android.provider.Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD
 import android.util.Log
-import com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser
 import com.android.systemui.Dumpable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.settings.SecureSettings
 import java.io.PrintWriter
 import javax.inject.Inject
@@ -50,6 +50,7 @@
     @Main private val handler: Handler,
     private val secureSettings: SecureSettings,
     private val contentResolver: ContentResolver,
+    private val selectedUserInteractor: SelectedUserInteractor,
     dumpManager: DumpManager
 ) : Dumpable {
 
@@ -134,7 +135,7 @@
                     )
             )
 
-            onChange(true, ArrayList(), 0, getCurrentUser())
+            onChange(true, ArrayList(), 0, selectedUserInteractor.getSelectedUserId())
         }
 
         private fun registerUri(uris: Collection<Uri>) {
@@ -153,29 +154,31 @@
             flags: Int,
             userId: Int
         ) {
-            if (getCurrentUser() != userId) {
+            if (selectedUserInteractor.getSelectedUserId() != userId) {
                 return
             }
 
             if (selfChange || uris.contains(wakeUri)) {
                 requestActiveUnlockOnWakeup = secureSettings.getIntForUser(
-                        ACTIVE_UNLOCK_ON_WAKE, 0, getCurrentUser()) == 1
+                        ACTIVE_UNLOCK_ON_WAKE, 0, selectedUserInteractor.getSelectedUserId()) == 1
             }
 
             if (selfChange || uris.contains(unlockIntentUri)) {
                 requestActiveUnlockOnUnlockIntent = secureSettings.getIntForUser(
-                        ACTIVE_UNLOCK_ON_UNLOCK_INTENT, 0, getCurrentUser()) == 1
+                        ACTIVE_UNLOCK_ON_UNLOCK_INTENT, 0,
+                        selectedUserInteractor.getSelectedUserId()) == 1
             }
 
             if (selfChange || uris.contains(bioFailUri)) {
                 requestActiveUnlockOnBioFail = secureSettings.getIntForUser(
-                        ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 0, getCurrentUser()) == 1
+                        ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 0,
+                        selectedUserInteractor.getSelectedUserId()) == 1
             }
 
             if (selfChange || uris.contains(faceErrorsUri)) {
                 processStringArray(
                         secureSettings.getStringForUser(ACTIVE_UNLOCK_ON_FACE_ERRORS,
-                                getCurrentUser()),
+                                selectedUserInteractor.getSelectedUserId()),
                         faceErrorsToTriggerBiometricFailOn,
                         setOf(FACE_ERROR_TIMEOUT))
             }
@@ -183,7 +186,7 @@
             if (selfChange || uris.contains(faceAcquireInfoUri)) {
                 processStringArray(
                         secureSettings.getStringForUser(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO,
-                                getCurrentUser()),
+                                selectedUserInteractor.getSelectedUserId()),
                         faceAcquireInfoToTriggerBiometricFailOn,
                         emptySet())
             }
@@ -192,7 +195,7 @@
                 processStringArray(
                         secureSettings.getStringForUser(
                                 ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
-                                getCurrentUser()),
+                                selectedUserInteractor.getSelectedUserId()),
                         onUnlockIntentWhenBiometricEnrolled,
                         setOf(BiometricType.NONE.intValue))
             }
@@ -201,7 +204,7 @@
                 processStringArray(
                     secureSettings.getStringForUser(
                         ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS,
-                        getCurrentUser()),
+                        selectedUserInteractor.getSelectedUserId()),
                     wakeupsConsideredUnlockIntents,
                     setOf(WAKE_REASON_UNFOLD_DEVICE))
             }
@@ -210,7 +213,7 @@
                 processStringArray(
                     secureSettings.getStringForUser(
                         ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD,
-                        getCurrentUser()),
+                        selectedUserInteractor.getSelectedUserId()),
                     wakeupsToForceDismissKeyguard,
                     setOf(WAKE_REASON_UNFOLD_DEVICE))
             }
@@ -316,7 +319,8 @@
         keyguardUpdateMonitor?.let {
             val anyFaceEnrolled = it.isFaceEnrolled
             val anyFingerprintEnrolled =
-                    it.getCachedIsUnlockWithFingerprintPossible(getCurrentUser())
+                    it.getCachedIsUnlockWithFingerprintPossible(
+                            selectedUserInteractor.getSelectedUserId())
             val udfpsEnrolled = it.isUdfpsEnrolled
 
             if (!anyFaceEnrolled && !anyFingerprintEnrolled) {
@@ -371,7 +375,8 @@
                     "${shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment()}")
             pw.println("   faceEnrolled=${it.isFaceEnrolled}")
             pw.println("   fpEnrolled=${
-                    it.getCachedIsUnlockWithFingerprintPossible(getCurrentUser())}")
+                    it.getCachedIsUnlockWithFingerprintPossible(
+                            selectedUserInteractor.getSelectedUserId())}")
             pw.println("   udfpsEnrolled=${it.isUdfpsEnrolled}")
         } ?: pw.println("   keyguardUpdateMonitor is uninitialized")
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
index 207f344..58bbdeb 100644
--- a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
@@ -45,6 +45,7 @@
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.keyguard.dagger.KeyguardBouncerScope;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 import java.util.NoSuchElementException;
 
@@ -63,6 +64,7 @@
     private Handler mHandler;
     private IKeyguardClient mClient;
     private KeyguardSecurityCallback mKeyguardCallback;
+    private SelectedUserInteractor mSelectedUserInteractor;
 
     private final ServiceConnection mConnection = new ServiceConnection() {
         @Override
@@ -76,7 +78,7 @@
                 } catch (RemoteException e) {
                     // Failed to link to death, just dismiss and unbind the service for now.
                     Log.e(TAG, "Lost connection to secondary lockscreen service", e);
-                    dismiss(KeyguardUpdateMonitor.getCurrentUser());
+                    dismiss(mSelectedUserInteractor.getSelectedUserId());
                 }
             }
         }
@@ -110,7 +112,7 @@
                 mView.setChildSurfacePackage(surfacePackage);
             } else {
                 mHandler.post(() -> {
-                    dismiss(KeyguardUpdateMonitor.getCurrentUser());
+                    dismiss(mSelectedUserInteractor.getSelectedUserId());
                 });
             }
         }
@@ -131,7 +133,7 @@
     protected SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
         @Override
         public void surfaceCreated(SurfaceHolder holder) {
-            final int userId = KeyguardUpdateMonitor.getCurrentUser();
+            final int userId = mSelectedUserInteractor.getSelectedUserId();
             mUpdateMonitor.registerCallback(mUpdateCallback);
 
             if (mClient != null) {
@@ -158,7 +160,7 @@
 
     private AdminSecondaryLockScreenController(Context context, KeyguardSecurityContainer parent,
             KeyguardUpdateMonitor updateMonitor, KeyguardSecurityCallback callback,
-            @Main Handler handler) {
+            @Main Handler handler, SelectedUserInteractor selectedUserInteractor) {
         mContext = context;
         mHandler = handler;
         mParent = parent;
@@ -166,6 +168,7 @@
         mKeyguardCallback = callback;
         mView = new AdminSecurityView(mContext, mSurfaceHolderCallback);
         mView.setId(View.generateViewId());
+        mSelectedUserInteractor = selectedUserInteractor;
     }
 
     /**
@@ -218,13 +221,13 @@
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error in onCreateKeyguardSurface", e);
-            dismiss(KeyguardUpdateMonitor.getCurrentUser());
+            dismiss(mSelectedUserInteractor.getSelectedUserId());
         }
     }
 
     private void dismiss(int userId) {
         mHandler.removeCallbacksAndMessages(null);
-        if (mView.isAttachedToWindow() && userId == KeyguardUpdateMonitor.getCurrentUser()) {
+        if (mView.isAttachedToWindow() && userId == mSelectedUserInteractor.getSelectedUserId()) {
             hide();
             if (mKeyguardCallback != null) {
                 mKeyguardCallback.dismiss(/* securityVerified= */ true, userId,
@@ -265,19 +268,24 @@
         private final KeyguardSecurityContainer mParent;
         private final KeyguardUpdateMonitor mUpdateMonitor;
         private final Handler mHandler;
+        private final SelectedUserInteractor mSelectedUserInteractor;
 
         @Inject
-        public Factory(Context context, KeyguardSecurityContainer parent,
-                KeyguardUpdateMonitor updateMonitor, @Main Handler handler) {
+        public Factory(Context context,
+                KeyguardSecurityContainer parent,
+                KeyguardUpdateMonitor updateMonitor,
+                @Main Handler handler,
+                SelectedUserInteractor selectedUserInteractor) {
             mContext = context;
             mParent = parent;
             mUpdateMonitor = updateMonitor;
             mHandler = handler;
+            mSelectedUserInteractor = selectedUserInteractor;
         }
 
         public AdminSecondaryLockScreenController create(KeyguardSecurityCallback callback) {
             return new AdminSecondaryLockScreenController(mContext, mParent, mUpdateMonitor,
-                    callback, mHandler);
+                    callback, mHandler, mSelectedUserInteractor);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
index f7e8eb4..5de370f 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
@@ -43,6 +43,7 @@
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.EmergencyDialerConstants;
 import com.android.systemui.util.ViewController;
 
@@ -67,6 +68,7 @@
     private LockPatternUtils mLockPatternUtils;
     private Executor mMainExecutor;
     private Executor mBackgroundExecutor;
+    private SelectedUserInteractor mSelectedUserInteractor;
 
     private final KeyguardUpdateMonitorCallback mInfoCallback =
             new KeyguardUpdateMonitorCallback() {
@@ -96,7 +98,8 @@
             ShadeController shadeController,
             @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger,
             LockPatternUtils lockPatternUtils,
-            Executor mainExecutor, Executor backgroundExecutor) {
+            Executor mainExecutor, Executor backgroundExecutor,
+            SelectedUserInteractor selectedUserInteractor) {
         super(view);
         mConfigurationController = configurationController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -109,6 +112,7 @@
         mLockPatternUtils = lockPatternUtils;
         mMainExecutor = mainExecutor;
         mBackgroundExecutor = backgroundExecutor;
+        mSelectedUserInteractor = selectedUserInteractor;
     }
 
     @Override
@@ -142,7 +146,7 @@
             mBackgroundExecutor.execute(() -> {
                 boolean isInCall = mTelecomManager != null && mTelecomManager.isInCall();
                 boolean isSecure = mLockPatternUtils
-                        .isSecure(KeyguardUpdateMonitor.getCurrentUser());
+                        .isSecure(mSelectedUserInteractor.getSelectedUserId());
                 mMainExecutor.execute(() -> mView.updateEmergencyCallButton(
                         /* isInCall= */ isInCall,
                         /* hasTelephonyRadio= */ getContext().getPackageManager()
@@ -192,7 +196,7 @@
 
                     getContext().startActivityAsUser(emergencyDialIntent,
                             ActivityOptions.makeCustomAnimation(getContext(), 0, 0).toBundle(),
-                            new UserHandle(KeyguardUpdateMonitor.getCurrentUser()));
+                            new UserHandle(mSelectedUserInteractor.getSelectedUserId()));
                 }
             });
         });
@@ -218,6 +222,7 @@
         private final LockPatternUtils mLockPatternUtils;
         private final Executor mMainExecutor;
         private final Executor mBackgroundExecutor;
+        private final SelectedUserInteractor mSelectedUserInteractor;
 
         @Inject
         public Factory(ConfigurationController configurationController,
@@ -227,7 +232,8 @@
                 @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger,
                 LockPatternUtils lockPatternUtils,
                 @Main Executor mainExecutor,
-                @Background Executor backgroundExecutor) {
+                @Background Executor backgroundExecutor,
+                SelectedUserInteractor selectedUserInteractor) {
 
             mConfigurationController = configurationController;
             mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -240,6 +246,7 @@
             mLockPatternUtils = lockPatternUtils;
             mMainExecutor = mainExecutor;
             mBackgroundExecutor = backgroundExecutor;
+            mSelectedUserInteractor = selectedUserInteractor;
         }
 
         /** Construct an {@link com.android.keyguard.EmergencyButtonController}. */
@@ -247,7 +254,7 @@
             return new EmergencyButtonController(view, mConfigurationController,
                     mKeyguardUpdateMonitor, mTelephonyManager, mPowerManager, mActivityTaskManager,
                     mShadeController, mTelecomManager, mMetricsLogger, mLockPatternUtils,
-                    mMainExecutor, mBackgroundExecutor);
+                    mMainExecutor, mBackgroundExecutor, mSelectedUserInteractor);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index 167bd59..dad4400 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -34,10 +34,11 @@
 import com.android.keyguard.EmergencyButtonController.EmergencyButtonCallback;
 import com.android.keyguard.KeyguardAbsKeyInputView.KeyDownListener;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.res.R;
 import com.android.systemui.classifier.FalsingClassifier;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.res.R;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -80,9 +81,9 @@
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             LatencyTracker latencyTracker, FalsingCollector falsingCollector,
             EmergencyButtonController emergencyButtonController,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor) {
         super(view, securityMode, keyguardSecurityCallback, emergencyButtonController,
-                messageAreaControllerFactory, featureFlags);
+                messageAreaControllerFactory, featureFlags, selectedUserInteractor);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mLockPatternUtils = lockPatternUtils;
         mLatencyTracker = latencyTracker;
@@ -104,7 +105,7 @@
         mEmergencyButtonController.setEmergencyButtonCallback(mEmergencyButtonCallback);
         // if the user is currently locked out, enforce it.
         long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
-                KeyguardUpdateMonitor.getCurrentUser());
+                mSelectedUserInteractor.getSelectedUserId());
         if (shouldLockout(deadline)) {
             handleAttemptLockout(deadline);
         }
@@ -175,7 +176,7 @@
     }
 
     void onPasswordChecked(int userId, boolean matched, int timeoutMs, boolean isValidPassword) {
-        boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId;
+        boolean dismissKeyguard = mSelectedUserInteractor.getSelectedUserId() == userId;
         if (matched) {
             getKeyguardSecurityCallback().reportUnlockAttempt(userId, true, 0);
             if (dismissKeyguard) {
@@ -212,7 +213,7 @@
             mPendingLockCheck.cancel(false);
         }
 
-        final int userId = KeyguardUpdateMonitor.getCurrentUser();
+        final int userId = mSelectedUserInteractor.getSelectedUserId();
         if (password.size() <= MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT) {
             // to avoid accidental lockout, only count attempts that are long enough to be a
             // real password. This may require some tweaking.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt
index e6a2bfa..d26caa9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.SessionTracker
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import java.io.PrintWriter
 import javax.inject.Inject
 
@@ -42,7 +43,8 @@
 class KeyguardBiometricLockoutLogger @Inject constructor(
     private val uiEventLogger: UiEventLogger,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
-    private val sessionTracker: SessionTracker
+    private val sessionTracker: SessionTracker,
+    private val selectedUserInteractor: SelectedUserInteractor
 ) : CoreStartable {
     private var fingerprintLockedOut = false
     private var faceLockedOut = false
@@ -52,7 +54,7 @@
 
     override fun start() {
         mKeyguardUpdateMonitorCallback.onStrongAuthStateChanged(
-                KeyguardUpdateMonitor.getCurrentUser())
+                selectedUserInteractor.getSelectedUserId())
         keyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback)
     }
 
@@ -79,7 +81,7 @@
         }
 
         override fun onStrongAuthStateChanged(userId: Int) {
-            if (userId != KeyguardUpdateMonitor.getCurrentUser()) {
+            if (userId != selectedUserInteractor.getSelectedUserId()) {
                 return
             }
             val strongAuthFlags = keyguardUpdateMonitor.strongAuthTracker
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 29ce18c..b309483 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -29,7 +29,6 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.res.R;
 import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
 import com.android.systemui.bouncer.ui.BouncerMessageView;
 import com.android.systemui.bouncer.ui.binder.BouncerMessageViewBinder;
@@ -38,7 +37,9 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.log.BouncerLogger;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.DevicePostureController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
@@ -51,7 +52,6 @@
 
     private final SecurityMode mSecurityMode;
     private final KeyguardSecurityCallback mKeyguardSecurityCallback;
-    private final EmergencyButton mEmergencyButton;
     private final EmergencyButtonController mEmergencyButtonController;
     private boolean mPaused;
     protected KeyguardMessageAreaController<BouncerKeyguardMessageArea> mMessageAreaController;
@@ -61,18 +61,20 @@
     // state for the current security method.
     private KeyguardSecurityCallback mNullCallback = new KeyguardSecurityCallback() {};
     private final FeatureFlags mFeatureFlags;
+    protected final SelectedUserInteractor mSelectedUserInteractor;
 
     protected KeyguardInputViewController(T view, SecurityMode securityMode,
             KeyguardSecurityCallback keyguardSecurityCallback,
             EmergencyButtonController emergencyButtonController,
             @Nullable KeyguardMessageAreaController.Factory messageAreaControllerFactory,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags,
+            SelectedUserInteractor selectedUserInteractor) {
         super(view);
         mSecurityMode = securityMode;
         mKeyguardSecurityCallback = keyguardSecurityCallback;
-        mEmergencyButton = view == null ? null : view.findViewById(R.id.emergency_call_button);
         mEmergencyButtonController = emergencyButtonController;
         mFeatureFlags = featureFlags;
+        mSelectedUserInteractor = selectedUserInteractor;
         if (messageAreaControllerFactory != null) {
             try {
                 BouncerKeyguardMessageArea kma = view.requireViewById(R.id.bouncer_message_area);
@@ -207,6 +209,7 @@
         private final DevicePostureController mDevicePostureController;
         private final KeyguardViewController mKeyguardViewController;
         private final FeatureFlags mFeatureFlags;
+        private final SelectedUserInteractor mSelectedUserInteractor;
 
         @Inject
         public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -219,7 +222,7 @@
                 EmergencyButtonController.Factory emergencyButtonControllerFactory,
                 DevicePostureController devicePostureController,
                 KeyguardViewController keyguardViewController,
-                FeatureFlags featureFlags) {
+                FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor) {
             mKeyguardUpdateMonitor = keyguardUpdateMonitor;
             mLockPatternUtils = lockPatternUtils;
             mLatencyTracker = latencyTracker;
@@ -234,6 +237,7 @@
             mDevicePostureController = devicePostureController;
             mKeyguardViewController = keyguardViewController;
             mFeatureFlags = featureFlags;
+            mSelectedUserInteractor = selectedUserInteractor;
         }
 
         /** Create a new {@link KeyguardInputViewController}. */
@@ -248,32 +252,32 @@
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mLatencyTracker, mFalsingCollector,
                         emergencyButtonController, mMessageAreaControllerFactory,
-                        mDevicePostureController, mFeatureFlags);
+                        mDevicePostureController, mFeatureFlags, mSelectedUserInteractor);
             } else if (keyguardInputView instanceof KeyguardPasswordView) {
                 return new KeyguardPasswordViewController((KeyguardPasswordView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
                         mInputMethodManager, emergencyButtonController, mMainExecutor, mResources,
                         mFalsingCollector, mKeyguardViewController,
-                        mDevicePostureController, mFeatureFlags);
+                        mDevicePostureController, mFeatureFlags, mSelectedUserInteractor);
             } else if (keyguardInputView instanceof KeyguardPINView) {
                 return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
                         mLiftToActivateListener, emergencyButtonController, mFalsingCollector,
-                        mDevicePostureController, mFeatureFlags);
+                        mDevicePostureController, mFeatureFlags, mSelectedUserInteractor);
             } else if (keyguardInputView instanceof KeyguardSimPinView) {
                 return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
                         mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
-                        emergencyButtonController, mFeatureFlags);
+                        emergencyButtonController, mFeatureFlags, mSelectedUserInteractor);
             } else if (keyguardInputView instanceof KeyguardSimPukView) {
                 return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
                         mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
-                        emergencyButtonController, mFeatureFlags);
+                        emergencyButtonController, mFeatureFlags, mSelectedUserInteractor);
             }
 
             throw new RuntimeException("Unable to find controller for " + keyguardInputView);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 959cf6f..2e21255 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -39,11 +39,12 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.res.R;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.DevicePostureController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import java.util.List;
@@ -51,7 +52,6 @@
 public class KeyguardPasswordViewController
         extends KeyguardAbsKeyInputViewController<KeyguardPasswordView> {
 
-    private static final int DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON = 500;  // 500ms
     private final KeyguardSecurityCallback mKeyguardSecurityCallback;
     private final DevicePostureController mPostureController;
     private final DevicePostureController.Callback mPostureCallback = posture ->
@@ -112,10 +112,11 @@
             FalsingCollector falsingCollector,
             KeyguardViewController keyguardViewController,
             DevicePostureController postureController,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags,
+            SelectedUserInteractor selectedUserInteractor) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, falsingCollector,
-                emergencyButtonController, featureFlags);
+                emergencyButtonController, featureFlags, selectedUserInteractor);
         mKeyguardSecurityCallback = keyguardSecurityCallback;
         mInputMethodManager = inputMethodManager;
         mPostureController = postureController;
@@ -132,7 +133,8 @@
     @Override
     protected void onViewAttached() {
         super.onViewAttached();
-        mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser()));
+        mPasswordEntry.setTextOperationUser(
+                UserHandle.of(mSelectedUserInteractor.getSelectedUserId()));
         mPasswordEntry.setKeyListener(TextKeyListener.getInstance());
         mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT
                 | InputType.TYPE_TEXT_VARIATION_PASSWORD);
@@ -164,13 +166,6 @@
 
         // If there's more than one IME, enable the IME switcher button
         updateSwitchImeButton();
-
-        // When we the current user is switching, InputMethodManagerService sometimes has not
-        // switched internal state yet here. As a quick workaround, we check the keyboard state
-        // again.
-        // TODO: Remove this workaround by ensuring such a race condition never happens.
-        mMainExecutor.executeDelayed(
-                this::updateSwitchImeButton, DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON);
     }
 
     @Override
@@ -187,7 +182,8 @@
 
     @Override
     void resetState() {
-        mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser()));
+        mPasswordEntry.setTextOperationUser(
+                UserHandle.of(mSelectedUserInteractor.getSelectedUserId()));
         mMessageAreaController.setMessage(getInitialMessageResId());
         final boolean wasDisabled = mPasswordEntry.isEnabled();
         mView.setPasswordEntryEnabled(true);
@@ -280,7 +276,7 @@
             final boolean shouldIncludeAuxiliarySubtypes) {
         final List<InputMethodInfo> enabledImis =
                 imm.getEnabledInputMethodListAsUser(
-                        UserHandle.of(KeyguardUpdateMonitor.getCurrentUser()));
+                        UserHandle.of(mSelectedUserInteractor.getSelectedUserId()));
 
         // Number of the filtered IMEs
         int filteredImisCount = 0;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 57151ae..db7ff88 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -36,11 +36,12 @@
 import com.android.internal.widget.LockscreenCredential;
 import com.android.keyguard.EmergencyButtonController.EmergencyButtonCallback;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.res.R;
 import com.android.systemui.classifier.FalsingClassifier;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.DevicePostureController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 import java.util.HashMap;
 import java.util.List;
@@ -110,7 +111,7 @@
                 mPendingLockCheck.cancel(false);
             }
 
-            final int userId = KeyguardUpdateMonitor.getCurrentUser();
+            final int userId = mSelectedUserInteractor.getSelectedUserId();
             if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
                 // Treat single-sized patterns as erroneous taps.
                 if (pattern.size() == 1) {
@@ -163,7 +164,7 @@
 
         private void onPatternChecked(int userId, boolean matched, int timeoutMs,
                 boolean isValidPattern) {
-            boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId;
+            boolean dismissKeyguard = mSelectedUserInteractor.getSelectedUserId() == userId;
             if (matched) {
                 getKeyguardSecurityCallback().reportUnlockAttempt(userId, true, 0);
                 if (dismissKeyguard) {
@@ -198,9 +199,10 @@
             FalsingCollector falsingCollector,
             EmergencyButtonController emergencyButtonController,
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
-            DevicePostureController postureController, FeatureFlags featureFlags) {
+            DevicePostureController postureController, FeatureFlags featureFlags,
+            SelectedUserInteractor selectedUserInteractor) {
         super(view, securityMode, keyguardSecurityCallback, emergencyButtonController,
-                messageAreaControllerFactory, featureFlags);
+                messageAreaControllerFactory, featureFlags, selectedUserInteractor);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mLockPatternUtils = lockPatternUtils;
         mLatencyTracker = latencyTracker;
@@ -223,7 +225,7 @@
         mLockPatternView.setOnPatternListener(new UnlockPatternListener());
         mLockPatternView.setSaveEnabled(false);
         mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
-                KeyguardUpdateMonitor.getCurrentUser()));
+                mSelectedUserInteractor.getSelectedUserId()));
         mLockPatternView.setOnTouchListener((v, event) -> {
             if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
                 mFalsingCollector.avoidGesture();
@@ -243,7 +245,7 @@
         mPostureController.addCallback(mPostureCallback);
         // if the user is currently locked out, enforce it.
         long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
-                KeyguardUpdateMonitor.getCurrentUser());
+                mSelectedUserInteractor.getSelectedUserId());
         if (deadline != 0) {
             handleAttemptLockout(deadline);
         }
@@ -266,7 +268,7 @@
     public void reset() {
         // reset lock pattern
         mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
-                KeyguardUpdateMonitor.getCurrentUser()));
+                mSelectedUserInteractor.getSelectedUserId()));
         mLockPatternView.enableInput();
         mLockPatternView.setEnabled(true);
         mLockPatternView.clearPattern();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index aacf866..b7d1171 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -25,9 +25,10 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.res.R;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.res.R;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinBasedInputView>
         extends KeyguardAbsKeyInputViewController<T> {
@@ -60,10 +61,11 @@
             LiftToActivateListener liftToActivateListener,
             EmergencyButtonController emergencyButtonController,
             FalsingCollector falsingCollector,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags,
+            SelectedUserInteractor selectedUserInteractor) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, falsingCollector,
-                emergencyButtonController, featureFlags);
+                emergencyButtonController, featureFlags, selectedUserInteractor);
         mLiftToActivateListener = liftToActivateListener;
         mFalsingCollector = falsingCollector;
         mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
@@ -74,7 +76,7 @@
         super.onViewAttached();
 
         boolean showAnimations = !mLockPatternUtils
-                .isPinEnhancedPrivacyEnabled(KeyguardUpdateMonitor.getCurrentUser());
+                .isPinEnhancedPrivacyEnabled(mSelectedUserInteractor.getSelectedUserId());
         mPasswordEntry.setShowPassword(showAnimations);
         for (NumPadKey button : mView.getButtons()) {
             button.setOnTouchListener((v, event) -> {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index 9a78868..947d90f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -23,11 +23,12 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.res.R;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.DevicePostureController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 public class KeyguardPinViewController
         extends KeyguardPinBasedInputViewController<KeyguardPINView> {
@@ -55,17 +56,17 @@
             EmergencyButtonController emergencyButtonController,
             FalsingCollector falsingCollector,
             DevicePostureController postureController,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, liftToActivateListener,
-                emergencyButtonController, falsingCollector, featureFlags);
+                emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mPostureController = postureController;
         mLockPatternUtils = lockPatternUtils;
         mFeatureFlags = featureFlags;
         view.setIsLockScreenLandscapeEnabled(mFeatureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE));
         mBackspaceKey = view.findViewById(R.id.delete_button);
-        mPinLength = mLockPatternUtils.getPinLength(KeyguardUpdateMonitor.getCurrentUser());
+        mPinLength = mLockPatternUtils.getPinLength(selectedUserInteractor.getSelectedUserId());
     }
 
     @Override
@@ -124,7 +125,7 @@
 
     private void updateAutoConfirmationState() {
         mDisabledAutoConfirmation = mLockPatternUtils.getCurrentFailedPasswordAttempts(
-                KeyguardUpdateMonitor.getCurrentUser()) >= MIN_FAILED_PIN_ATTEMPTS;
+                mSelectedUserInteractor.getSelectedUserId()) >= MIN_FAILED_PIN_ATTEMPTS;
         updateOKButtonVisibility();
         updateBackSpaceVisibility();
         updatePinHinting();
@@ -179,7 +180,8 @@
      */
     private boolean isAutoPinConfirmEnabledInSettings() {
         //Checks if user has enabled the auto confirm in Settings
-        return mLockPatternUtils.isAutoPinConfirmEnabled(KeyguardUpdateMonitor.getCurrentUser())
+        return mLockPatternUtils.isAutoPinConfirmEnabled(
+                mSelectedUserInteractor.getSelectedUserId())
                 && mPinLength != LockPatternUtils.PIN_LENGTH_UNAVAILABLE;
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 51dafac..7101ed5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -91,7 +91,7 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
-import com.android.systemui.user.domain.interactor.UserInteractor;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.settings.GlobalSettings;
@@ -157,23 +157,25 @@
     private int mCurrentUser = UserHandle.USER_NULL;
     private UserSwitcherController.UserSwitchCallback mUserSwitchCallback =
             new UserSwitcherController.UserSwitchCallback() {
-        @Override
-        public void onUserSwitched() {
-            if (mCurrentUser == KeyguardUpdateMonitor.getCurrentUser()) {
-                return;
-            }
-            mCurrentUser = KeyguardUpdateMonitor.getCurrentUser();
-            showPrimarySecurityScreen(false);
-            if (mCurrentSecurityMode != SecurityMode.SimPin
-                    && mCurrentSecurityMode != SecurityMode.SimPuk) {
-                reinflateViewFlipper((l) -> {});
-            }
-        }
-    };
+                @Override
+                public void onUserSwitched() {
+                    if (mCurrentUser == mSelectedUserInteractor.getSelectedUserId()) {
+                        return;
+                    }
+                    mCurrentUser = mSelectedUserInteractor.getSelectedUserId();
+                    showPrimarySecurityScreen(false);
+                    if (mCurrentSecurityMode != SecurityMode.SimPin
+                            && mCurrentSecurityMode != SecurityMode.SimPuk) {
+                        reinflateViewFlipper((l) -> {
+                        });
+                    }
+                }
+            };
 
     @VisibleForTesting
     final Gefingerpoken mGlobalTouchListener = new Gefingerpoken() {
         private MotionEvent mTouchDown;
+
         @Override
         public boolean onInterceptTouchEvent(MotionEvent ev) {
             return false;
@@ -267,7 +269,8 @@
                 ThreadUtils.postOnBackgroundThread(() -> {
                     try {
                         Thread.sleep(5000);
-                    } catch (InterruptedException ignored) { }
+                    } catch (InterruptedException ignored) {
+                    }
                     System.gc();
                     System.runFinalization();
                     System.gc();
@@ -281,7 +284,7 @@
             mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER)
                     .setType(success ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_FAILURE));
             mUiEventLogger.log(success ? BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS
-                            : BouncerUiEvent.BOUNCER_PASSWORD_FAILURE, getSessionId());
+                    : BouncerUiEvent.BOUNCER_PASSWORD_FAILURE, getSessionId());
         }
 
         @Override
@@ -404,7 +407,7 @@
                         }
                         mKeyguardSecurityCallback.dismiss(
                                 false /* authenticated */,
-                                KeyguardUpdateMonitor.getCurrentUser(),
+                                mSelectedUserInteractor.getSelectedUserId(),
                                 /* bypassSecondaryLockScreen */ false,
                                 SecurityMode.Invalid
                         );
@@ -420,12 +423,13 @@
                     showPrimarySecurityScreen(false);
                 }
             };
-    private final UserInteractor mUserInteractor;
+    private final SelectedUserInteractor mSelectedUserInteractor;
     private final Provider<DeviceEntryInteractor> mDeviceEntryInteractor;
     private final Provider<JavaAdapter> mJavaAdapter;
     private final DeviceProvisionedController mDeviceProvisionedController;
     private final Lazy<PrimaryBouncerInteractor> mPrimaryBouncerInteractor;
-    @Nullable private Job mSceneTransitionCollectionJob;
+    @Nullable
+    private Job mSceneTransitionCollectionJob;
 
     @Inject
     public KeyguardSecurityContainerController(KeyguardSecurityContainer view,
@@ -453,7 +457,7 @@
             KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
             BouncerMessageInteractor bouncerMessageInteractor,
             Provider<JavaAdapter> javaAdapter,
-            UserInteractor userInteractor,
+            SelectedUserInteractor selectedUserInteractor,
             DeviceProvisionedController deviceProvisionedController,
             FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
@@ -487,7 +491,7 @@
         mAudioManager = audioManager;
         mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
         mBouncerMessageInteractor = bouncerMessageInteractor;
-        mUserInteractor = userInteractor;
+        mSelectedUserInteractor = selectedUserInteractor;
         mDeviceEntryInteractor = deviceEntryInteractor;
         mJavaAdapter = javaAdapter;
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
@@ -520,10 +524,10 @@
             // When the scene framework says that the lockscreen has been dismissed, dismiss the
             // keyguard here, revealing the underlying app or launcher:
             mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow(
-                mDeviceEntryInteractor.get().isDeviceEntered(),
+                    mDeviceEntryInteractor.get().isDeviceEntered(),
                     isDeviceEntered -> {
                     if (isDeviceEntered) {
-                        final int selectedUserId = mUserInteractor.getSelectedUserId();
+                        final int selectedUserId = mSelectedUserInteractor.getSelectedUserId();
                         showNextSecurityScreenOrFinish(
                             /* authenticated= */ true,
                             selectedUserId,
@@ -548,7 +552,7 @@
         }
     }
 
-    /** */
+    /**  */
     public void onPause() {
         if (DEBUG) {
             Log.d(TAG, String.format("screen off, instance %s at %s",
@@ -586,12 +590,13 @@
     /**
      * Shows the primary security screen for the user. This will be either the multi-selector
      * or the user's security method.
+     *
      * @param turningOff true if the device is being turned off
      */
     public void showPrimarySecurityScreen(boolean turningOff) {
         if (DEBUG) Log.d(TAG, "show()");
         SecurityMode securityMode = whitelistIpcs(() -> mSecurityModel.getSecurityMode(
-                KeyguardUpdateMonitor.getCurrentUser()));
+                mSelectedUserInteractor.getSelectedUserId()));
         if (DEBUG) Log.v(TAG, "showPrimarySecurityScreen(turningOff=" + turningOff + ")");
         showSecurityScreen(securityMode);
     }
@@ -671,6 +676,7 @@
 
     /**
      * Dismisses the keyguard by going to the next screen or making it gone.
+     *
      * @param targetUserId a user that needs to be the foreground user at the dismissal completion.
      * @return True if the keyguard is done.
      */
@@ -716,7 +722,7 @@
     }
 
     /**
-     *  Resets the state of the views.
+     * Resets the state of the views.
      */
     public void reset() {
         mView.reset();
@@ -748,7 +754,7 @@
             getCurrentSecurityController(controller -> controller.onResume(reason));
         }
         mView.onResume(
-                mSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser()),
+                mSecurityModel.getSecurityMode(mSelectedUserInteractor.getSelectedUserId()),
                 mKeyguardStateController.isFaceAuthEnabled());
     }
 
@@ -764,7 +770,6 @@
 
     /**
      * Show the bouncer and start appear animations.
-     *
      */
     public void appear() {
         // We might still be collapsed and the view didn't have time to layout yet or still
@@ -823,13 +828,16 @@
 
     /**
      * Shows the next security screen if there is one.
-     * @param authenticated true if the user entered the correct authentication
-     * @param targetUserId a user that needs to be the foreground user at the finish (if called)
-     *     completion.
+     *
+     * @param authenticated             true if the user entered the correct authentication
+     * @param targetUserId              a user that needs to be the foreground user at the finish
+     *                                  (if called)
+     *                                  completion.
      * @param bypassSecondaryLockScreen true if the user is allowed to bypass the secondary
-     *     secondary lock screen requirement, if any.
-     * @param expectedSecurityMode SecurityMode that is invoking this request. SecurityMode.Invalid
-     *      indicates that no check should be done
+     *                                  secondary lock screen requirement, if any.
+     * @param expectedSecurityMode      SecurityMode that is invoking this request.
+     *                                  SecurityMode.Invalid
+     *                                  indicates that no check should be done
      * @return true if keyguard is done
      */
     public boolean showNextSecurityScreenOrFinish(boolean authenticated, int targetUserId,
@@ -879,7 +887,7 @@
                     // Shortcut for SIM PIN/PUK to go to directly to user's security screen or home
                     SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId);
                     boolean isLockscreenDisabled = mLockPatternUtils.isLockScreenDisabled(
-                            KeyguardUpdateMonitor.getCurrentUser())
+                            mSelectedUserInteractor.getSelectedUserId())
                             || !mDeviceProvisionedController.isUserSetup(targetUserId);
 
                     if (securityMode == SecurityMode.None && isLockscreenDisabled) {
@@ -955,6 +963,7 @@
      * Allows the media keys to work when the keyguard is showing.
      * The media keys should be of no interest to the actual keyguard view(s),
      * so intercepting them here should not be of any harm.
+     *
      * @param event The key event
      * @return whether the event was consumed as a media key.
      */
@@ -1050,8 +1059,6 @@
     /**
      * Switches to the given security view unless it's already being shown, in which case
      * this is a no-op.
-     *
-     * @param securityMode
      */
     @VisibleForTesting
     void showSecurityScreen(SecurityMode securityMode) {
@@ -1230,6 +1237,7 @@
      * Fades and translates in/out the security screen.
      * Fades in as expansion approaches 0.
      * Animation duration is between 0.33f and 0.67f of panel expansion fraction.
+     *
      * @param fraction amount of the screen that should show.
      */
     public void setExpansion(float fraction) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index d2d0517..6e24208 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -43,6 +43,7 @@
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.res.R;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 public class KeyguardSimPinViewController
         extends KeyguardPinBasedInputViewController<KeyguardSimPinView> {
@@ -83,10 +84,11 @@
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
             TelephonyManager telephonyManager, FalsingCollector falsingCollector,
-            EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags) {
+            EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags,
+            SelectedUserInteractor selectedUserInteractor) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, liftToActivateListener,
-                emergencyButtonController, falsingCollector, featureFlags);
+                emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mTelephonyManager = telephonyManager;
         mSimImageView = mView.findViewById(R.id.keyguard_sim);
@@ -168,7 +170,7 @@
                             mRemainingAttempts = -1;
                             mShowDefaultMessage = true;
                             getKeyguardSecurityCallback().dismiss(
-                                    true, KeyguardUpdateMonitor.getCurrentUser(),
+                                    true, mSelectedUserInteractor.getSelectedUserId(),
                                     SecurityMode.SimPin);
                         } else {
                             mShowDefaultMessage = false;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index b52a36b..13f9d3e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -40,6 +40,7 @@
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.res.R;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 public class KeyguardSimPukViewController
         extends KeyguardPinBasedInputViewController<KeyguardSimPukView> {
@@ -70,7 +71,8 @@
             if (simState == TelephonyManager.SIM_STATE_READY) {
                 mRemainingAttempts = -1;
                 mShowDefaultMessage = true;
-                getKeyguardSecurityCallback().dismiss(true, KeyguardUpdateMonitor.getCurrentUser(),
+                getKeyguardSecurityCallback().dismiss(
+                        true, mSelectedUserInteractor.getSelectedUserId(),
                         SecurityMode.SimPuk);
             } else {
                 resetState();
@@ -87,10 +89,11 @@
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
             TelephonyManager telephonyManager, FalsingCollector falsingCollector,
-            EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags) {
+            EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags,
+            SelectedUserInteractor selectedUserInteractor) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, liftToActivateListener,
-                emergencyButtonController, falsingCollector, featureFlags);
+                emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mTelephonyManager = telephonyManager;
         mSimImageView = mView.findViewById(R.id.keyguard_sim);
@@ -284,7 +287,7 @@
                             mShowDefaultMessage = true;
 
                             getKeyguardSecurityCallback().dismiss(
-                                    true, KeyguardUpdateMonitor.getCurrentUser(),
+                                    true, mSelectedUserInteractor.getSelectedUserId(),
                                     SecurityMode.SimPuk);
                         } else {
                             mShowDefaultMessage = false;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 205c297..7d6240b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -186,8 +186,8 @@
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt;
 import com.android.systemui.telephony.TelephonyListenerManager;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.Assert;
-import com.android.systemui.util.settings.SecureSettings;
 
 import dalvik.annotation.optimization.NeverCompile;
 
@@ -410,7 +410,6 @@
     private final DevicePolicyManager mDevicePolicyManager;
     private final DevicePostureController mPostureController;
     private final BroadcastDispatcher mBroadcastDispatcher;
-    private final SecureSettings mSecureSettings;
     private final InteractionJankMonitor mInteractionJankMonitor;
     private final LatencyTracker mLatencyTracker;
     private final StatusBarStateController mStatusBarStateController;
@@ -429,6 +428,7 @@
     private final IActivityTaskManager mActivityTaskManager;
     private final WakefulnessLifecycle mWakefulness;
     private final DisplayTracker mDisplayTracker;
+    private final SelectedUserInteractor mSelectedUserInteractor;
     private final LockPatternUtils mLockPatternUtils;
     @VisibleForTesting
     @DevicePostureInt
@@ -537,13 +537,14 @@
 
     private static int sCurrentUser;
 
+    @Deprecated
     public synchronized static void setCurrentUser(int currentUser) {
         sCurrentUser = currentUser;
     }
 
     /**
      * @deprecated This can potentially return unexpected values in a multi user scenario
-     * as this state is managed by another component. Consider using {@link UserTracker}.
+     * as this state is managed by another component. Consider using {@link SelectedUserInteractor}.
      */
     @Deprecated
     public synchronized static int getCurrentUser() {
@@ -577,7 +578,7 @@
 
         if (enabled) {
             String message = null;
-            if (KeyguardUpdateMonitor.getCurrentUser() == userId
+            if (mSelectedUserInteractor.getSelectedUserId() == userId
                     && trustGrantedMessages != null) {
                 // Show the first non-empty string provided by a trust agent OR intentionally pass
                 // an empty string through (to prevent the default trust agent string from showing)
@@ -590,7 +591,7 @@
             }
 
             mLogger.logTrustGrantedWithFlags(flags, newlyUnlocked, userId, message);
-            if (userId == getCurrentUser()) {
+            if (userId == mSelectedUserInteractor.getSelectedUserId()) {
                 if (newlyUnlocked) {
                     // if this callback is ever removed, this should then be logged in
                     // TrustRepository
@@ -1038,7 +1039,7 @@
             mHandler.removeCallbacks(mFpCancelNotReceived);
         }
         try {
-            final int userId = mUserTracker.getUserId();
+            final int userId = mSelectedUserInteractor.getSelectedUserId(true);
             if (userId != authUserId) {
                 mLogger.logFingerprintAuthForWrongUser(authUserId);
                 return;
@@ -1127,8 +1128,8 @@
             lockedOutStateChanged = !mFingerprintLockedOutPermanent;
             mFingerprintLockedOutPermanent = true;
             mLogger.d("Fingerprint permanently locked out - requiring stronger auth");
-            mLockPatternUtils.requireStrongAuth(
-                    STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, getCurrentUser());
+            mLockPatternUtils.requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT,
+                    mSelectedUserInteractor.getSelectedUserId());
         }
 
         if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT
@@ -1309,7 +1310,7 @@
                 mLogger.d("Aborted successful auth because device is going to sleep.");
                 return;
             }
-            final int userId = mUserTracker.getUserId();
+            final int userId = mSelectedUserInteractor.getSelectedUserId(true);
             if (userId != authUserId) {
                 mLogger.logFaceAuthForWrongUser(authUserId);
                 return;
@@ -1565,7 +1566,8 @@
     @Deprecated
     public boolean getIsFaceAuthenticated() {
         boolean faceAuthenticated = false;
-        BiometricAuthenticated bioFaceAuthenticated = mUserFaceAuthenticated.get(getCurrentUser());
+        BiometricAuthenticated bioFaceAuthenticated =
+                mUserFaceAuthenticated.get(mSelectedUserInteractor.getSelectedUserId());
         if (bioFaceAuthenticated != null) {
             faceAuthenticated = bioFaceAuthenticated.mAuthenticated;
         }
@@ -1754,9 +1756,10 @@
                 cb.onStrongAuthStateChanged(userId);
             }
         }
-        if (userId == getCurrentUser()) {
+        if (userId == mSelectedUserInteractor.getSelectedUserId()) {
             FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED.setExtraInfo(
-                    mStrongAuthTracker.getStrongAuthForUser(getCurrentUser()));
+                    mStrongAuthTracker.getStrongAuthForUser(
+                            mSelectedUserInteractor.getSelectedUserId()));
 
             // Strong auth is only reset when primary auth is used to enter the device,
             // so we only check whether to stop biometric listening states here
@@ -1783,10 +1786,10 @@
                 cb.onNonStrongBiometricAllowedChanged(userId);
             }
         }
-        if (userId == getCurrentUser()) {
+        if (userId == mSelectedUserInteractor.getSelectedUserId()) {
             FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED.setExtraInfo(
                     mStrongAuthTracker.isNonStrongBiometricAllowedAfterIdleTimeout(
-                            getCurrentUser()) ? -1 : 1);
+                            mSelectedUserInteractor.getSelectedUserId()) ? -1 : 1);
 
             // This is only reset when primary auth is used to enter the device, so we only check
             // whether to stop biometric listening states here
@@ -2191,12 +2194,12 @@
         }
 
         public boolean isUnlockingWithBiometricAllowed(boolean isStrongBiometric) {
-            int userId = getCurrentUser();
+            int userId = mSelectedUserInteractor.getSelectedUserId();
             return isBiometricAllowedForUser(isStrongBiometric, userId);
         }
 
         public boolean hasUserAuthenticatedSinceBoot() {
-            int userId = getCurrentUser();
+            int userId = mSelectedUserInteractor.getSelectedUserId();
             return (getStrongAuthForUser(userId)
                     & STRONG_AUTH_REQUIRED_AFTER_BOOT) == 0;
         }
@@ -2344,7 +2347,6 @@
             UserTracker userTracker,
             @Main Looper mainLooper,
             BroadcastDispatcher broadcastDispatcher,
-            SecureSettings secureSettings,
             DumpManager dumpManager,
             @Background Executor backgroundExecutor,
             @Main Executor mainExecutor,
@@ -2376,7 +2378,8 @@
             TaskStackChangeListeners taskStackChangeListeners,
             IActivityTaskManager activityTaskManagerService,
             DisplayTracker displayTracker,
-            WakefulnessLifecycle wakefulness) {
+            WakefulnessLifecycle wakefulness,
+            SelectedUserInteractor selectedUserInteractor) {
         mContext = context;
         mSubscriptionManager = subscriptionManager;
         mUserTracker = userTracker;
@@ -2392,7 +2395,6 @@
         mStatusBarState = mStatusBarStateController.getState();
         mLockPatternUtils = lockPatternUtils;
         mAuthController = authController;
-        mSecureSettings = secureSettings;
         dumpManager.registerDumpable(this);
         mSensorPrivacyManager = sensorPrivacyManager;
         mActiveUnlockConfig = activeUnlockConfiguration;
@@ -2426,6 +2428,7 @@
         mWakefulness = wakefulness;
         mDisplayTracker = displayTracker;
         mDisplayTracker.addDisplayChangeCallback(mDisplayCallback, mainExecutor);
+        mSelectedUserInteractor = selectedUserInteractor;
 
         mHandler = new Handler(mainLooper) {
             @Override
@@ -2648,7 +2651,7 @@
 
         mTaskStackChangeListeners.registerTaskStackListener(mTaskStackListener);
         mIsSystemUser = mUserManager.isSystemUser();
-        int user = mUserTracker.getUserId();
+        int user = mSelectedUserInteractor.getSelectedUserId(true);
         mUserIsUnlocked.put(user, mUserManager.isUserUnlocked(user));
         mLogoutEnabled = mDevicePolicyManager.isLogoutEnabled();
         updateSecondaryLockscreenRequirement(user);
@@ -2720,7 +2723,7 @@
      * @return true if there's at least one udfps enrolled for the current user.
      */
     public boolean isUdfpsEnrolled() {
-        return mAuthController.isUdfpsEnrolled(getCurrentUser());
+        return mAuthController.isUdfpsEnrolled(mSelectedUserInteractor.getSelectedUserId());
     }
 
     /**
@@ -2735,7 +2738,7 @@
      * @return true if there's at least one sfps enrollment for the current user.
      */
     public boolean isSfpsEnrolled() {
-        return mAuthController.isSfpsEnrolled(getCurrentUser());
+        return mAuthController.isSfpsEnrolled(mSelectedUserInteractor.getSelectedUserId());
     }
 
     /**
@@ -2906,7 +2909,7 @@
 
         if (shouldTriggerActiveUnlock()) {
             mLogger.logActiveUnlockTriggered(reason);
-            mTrustManager.reportUserMayRequestUnlock(KeyguardUpdateMonitor.getCurrentUser());
+            mTrustManager.reportUserMayRequestUnlock(mSelectedUserInteractor.getSelectedUserId());
         }
     }
 
@@ -2960,7 +2963,7 @@
 
         if (allowRequest && shouldTriggerActiveUnlock()) {
             mLogger.logUserRequestedUnlock(requestOrigin, reason, dismissKeyguard);
-            mTrustManager.reportUserRequestedUnlock(KeyguardUpdateMonitor.getCurrentUser(),
+            mTrustManager.reportUserRequestedUnlock(mSelectedUserInteractor.getSelectedUserId(),
                     dismissKeyguard);
         }
     }
@@ -3031,7 +3034,7 @@
                 && mStatusBarState != StatusBarState.SHADE_LOCKED);
 
         // Gates:
-        final int user = getCurrentUser();
+        final int user = mSelectedUserInteractor.getSelectedUserId();
 
         // No need to trigger active unlock if we're already unlocked or don't have
         // pin/pattern/password setup
@@ -3073,30 +3076,33 @@
     }
 
     private boolean shouldListenForFingerprintAssistant() {
-        BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(getCurrentUser());
+        BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(
+                mSelectedUserInteractor.getSelectedUserId());
         return mAssistantVisible && mKeyguardOccluded
                 && !(fingerprint != null && fingerprint.mAuthenticated)
-                && !mUserHasTrust.get(getCurrentUser(), false);
+                && !mUserHasTrust.get(
+                        mSelectedUserInteractor.getSelectedUserId(), false);
     }
 
     private boolean shouldListenForFaceAssistant() {
-        BiometricAuthenticated face = mUserFaceAuthenticated.get(getCurrentUser());
+        BiometricAuthenticated face = mUserFaceAuthenticated.get(
+                mSelectedUserInteractor.getSelectedUserId());
         return mAssistantVisible
                 // There can be intermediate states where mKeyguardShowing is false but
                 // mKeyguardOccluded is true, we don't want to run face auth in such a scenario.
                 && (mKeyguardShowing && mKeyguardOccluded)
                 && !(face != null && face.mAuthenticated)
-                && !mUserHasTrust.get(getCurrentUser(), false);
+                && !mUserHasTrust.get(mSelectedUserInteractor.getSelectedUserId(), false);
     }
 
     private boolean shouldTriggerActiveUnlockForAssistant() {
         return mAssistantVisible && mKeyguardOccluded
-                && !mUserHasTrust.get(getCurrentUser(), false);
+                && !mUserHasTrust.get(mSelectedUserInteractor.getSelectedUserId(), false);
     }
 
     @VisibleForTesting
     protected boolean shouldListenForFingerprint(boolean isUdfps) {
-        final int user = getCurrentUser();
+        final int user = mSelectedUserInteractor.getSelectedUserId();
         final boolean userDoesNotHaveTrust = !getUserHasTrust(user);
         final boolean shouldListenForFingerprintAssistant = shouldListenForFingerprintAssistant();
         final boolean shouldListenKeyguardState =
@@ -3185,7 +3191,7 @@
         final boolean statusBarShadeLocked = mStatusBarState == StatusBarState.SHADE_LOCKED;
         final boolean awakeKeyguard = isKeyguardVisible() && mDeviceInteractive
                 && !statusBarShadeLocked;
-        final int user = getCurrentUser();
+        final int user = mSelectedUserInteractor.getSelectedUserId();
         final boolean faceAuthAllowed = isUnlockingWithBiometricAllowed(FACE);
         final boolean canBypass = mKeyguardBypassController != null
                 && mKeyguardBypassController.canBypass();
@@ -3285,7 +3291,7 @@
     }
 
     private void startListeningForFingerprint() {
-        final int userId = getCurrentUser();
+        final int userId = mSelectedUserInteractor.getSelectedUserId();
         final boolean unlockPossible = isUnlockWithFingerprintPossible(userId);
         if (mFingerprintCancelSignal != null) {
             mLogger.logUnexpectedFpCancellationSignalState(
@@ -3332,7 +3338,7 @@
     }
 
     private void startListeningForFace(@NonNull FaceAuthUiEvent faceAuthUiEvent) {
-        final int userId = getCurrentUser();
+        final int userId = mSelectedUserInteractor.getSelectedUserId();
         final boolean unlockPossible = isUnlockWithFacePossible(userId);
         if (mFaceCancelSignal != null) {
             mLogger.logUnexpectedFaceCancellationSignalState(mFaceRunningState, unlockPossible);
@@ -3875,12 +3881,12 @@
     }
 
     private boolean resolveNeedsSlowUnlockTransition() {
-        if (isUserUnlocked(getCurrentUser())) {
+        if (isUserUnlocked(mSelectedUserInteractor.getSelectedUserId())) {
             return false;
         }
         Intent homeIntent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME);
         ResolveInfo resolveInfo = mPackageManager.resolveActivityAsUser(homeIntent,
-                0 /* flags */, getCurrentUser());
+                0 /* flags */, mSelectedUserInteractor.getSelectedUserId());
 
         if (resolveInfo == null) {
             mLogger.w("resolveNeedsSlowUnlockTransition: returning false since activity could "
@@ -4066,9 +4072,11 @@
     @AnyThread
     public void setSwitchingUser(boolean switching) {
         if (switching) {
-            mLogger.logUserSwitching(getCurrentUser(), "from setSwitchingUser");
+            mLogger.logUserSwitching(
+                    mSelectedUserInteractor.getSelectedUserId(), "from setSwitchingUser");
         } else {
-            mLogger.logUserSwitchComplete(getCurrentUser(), "from setSwitchingUser");
+            mLogger.logUserSwitchComplete(
+                    mSelectedUserInteractor.getSelectedUserId(), "from setSwitchingUser");
         }
         mSwitchingUser = switching;
         // Since this comes in on a binder thread, we need to post it first
@@ -4443,9 +4451,10 @@
     @Override
     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("KeyguardUpdateMonitor state:");
-        pw.println("  getUserHasTrust()=" + getUserHasTrust(getCurrentUser()));
+        pw.println("  getUserHasTrust()=" + getUserHasTrust(
+                mSelectedUserInteractor.getSelectedUserId()));
         pw.println("  getUserUnlockedWithBiometric()="
-                + getUserUnlockedWithBiometric(getCurrentUser()));
+                + getUserUnlockedWithBiometric(mSelectedUserInteractor.getSelectedUserId()));
         pw.println("  isFaceAuthInteractorEnabled: " + isFaceAuthInteractorEnabled());
         pw.println("  SIM States:");
         for (SimData data : mSimDatas.values()) {
@@ -4463,7 +4472,7 @@
             pw.println("    " + subId + "=" + mServiceStates.get(subId));
         }
         if (isFingerprintSupported()) {
-            final int userId = mUserTracker.getUserId();
+            final int userId = mSelectedUserInteractor.getSelectedUserId(true);
             final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
             BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId);
             pw.println("  Fingerprint state (user=" + userId + ")");
@@ -4506,7 +4515,7 @@
                     mFingerprintListenBuffer.toList()
             ).printTableData(pw);
         } else if (mFpm != null && mFingerprintSensorProperties.isEmpty()) {
-            final int userId = mUserTracker.getUserId();
+            final int userId = mSelectedUserInteractor.getSelectedUserId(true);
             pw.println("  Fingerprint state (user=" + userId + ")");
             pw.println("    mFingerprintSensorProperties.isEmpty="
                     + mFingerprintSensorProperties.isEmpty());
@@ -4520,7 +4529,7 @@
             ).printTableData(pw);
         }
         if (isFaceSupported()) {
-            final int userId = mUserTracker.getUserId();
+            final int userId = mSelectedUserInteractor.getSelectedUserId(true);
             final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
             BiometricAuthenticated face = mUserFaceAuthenticated.get(userId);
             pw.println("  Face authentication state (user=" + userId + ")");
@@ -4550,7 +4559,7 @@
                     mFaceListenBuffer.toList()
             ).printTableData(pw);
         } else if (mFaceManager != null && mFaceSensorProperties.isEmpty()) {
-            final int userId = mUserTracker.getUserId();
+            final int userId = mSelectedUserInteractor.getSelectedUserId(true);
             pw.println("  Face state (user=" + userId + ")");
             pw.println("    mFaceSensorProperties.isEmpty="
                     + mFaceSensorProperties.isEmpty());
@@ -4564,7 +4573,7 @@
             ).printTableData(pw);
         }
         pw.println("ActiveUnlockRunning="
-                + mTrustManager.isActiveUnlockRunning(KeyguardUpdateMonitor.getCurrentUser()));
+                + mTrustManager.isActiveUnlockRunning(mSelectedUserInteractor.getSelectedUserId()));
         new DumpsysTableLogger(
                 "KeyguardActiveUnlockTriggers",
                 KeyguardActiveUnlockModel.TABLE_HEADERS,
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index 40d0be1..ff6a3d0 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -25,7 +25,6 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.View;
@@ -105,18 +104,6 @@
         mLockIcon.setImageTintList(ColorStateList.valueOf(mLockIconColor));
     }
 
-    void setImageDrawable(Drawable drawable) {
-        mLockIcon.setImageDrawable(drawable);
-
-        if (!mUseBackground) return;
-
-        if (drawable == null) {
-            mBgView.setVisibility(View.INVISIBLE);
-        } else {
-            mBgView.setVisibility(View.VISIBLE);
-        }
-    }
-
     /**
      * Whether or not to render the lock icon background. Mainly used for UDPFS.
      */
@@ -197,6 +184,7 @@
         mLockIcon = new ImageView(context, attrs);
         mLockIcon.setId(R.id.lock_icon);
         mLockIcon.setScaleType(ImageView.ScaleType.CENTER_CROP);
+        mLockIcon.setImageDrawable(context.getDrawable(R.drawable.super_lock_icon));
         addView(mLockIcon);
         LayoutParams lp = (LayoutParams) mLockIcon.getLayoutParams();
         lp.height = MATCH_PARENT;
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 83da80f..611283f 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -35,7 +35,6 @@
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.graphics.drawable.AnimatedStateListDrawable;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricSourceType;
 import android.os.Process;
@@ -120,9 +119,6 @@
     private boolean mUdfpsEnrolled;
     private Resources mResources;
     private Context mContext;
-
-    @NonNull private final AnimatedStateListDrawable mIcon;
-
     @NonNull private CharSequence mUnlockedLabel;
     @NonNull private CharSequence mLockedLabel;
     @NonNull private final VibratorHelper mVibrator;
@@ -147,7 +143,6 @@
     private boolean mCanDismissLockScreen;
     private int mStatusBarState;
     private boolean mIsKeyguardShowing;
-    private Runnable mOnGestureDetectedRunnable;
     private Runnable mLongPressCancelRunnable;
 
     private boolean mUdfpsSupported;
@@ -232,9 +227,6 @@
 
         mMaxBurnInOffsetX = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
         mMaxBurnInOffsetY = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
-
-        mIcon = (AnimatedStateListDrawable)
-                resources.getDrawable(R.drawable.super_lock_icon, context.getTheme());
         mUnlockedLabel = resources.getString(R.string.accessibility_unlock_button);
         mLockedLabel = resources.getString(R.string.accessibility_lock_icon);
         mLongPressTimeout = resources.getInteger(R.integer.config_lockIconLongPress);
@@ -270,7 +262,6 @@
     @SuppressLint("ClickableViewAccessibility")
     public void setLockIconView(LockIconView lockIconView) {
         mView = lockIconView;
-        mView.setImageDrawable(mIcon);
         mView.setAccessibilityDelegate(mAccessibilityDelegate);
 
         if (mFeatureFlags.isEnabled(DOZING_MIGRATION_1)) {
@@ -492,10 +483,6 @@
         pw.println("mUdfpsSupported: " + mUdfpsSupported);
         pw.println("mUdfpsEnrolled: " + mUdfpsEnrolled);
         pw.println("mIsKeyguardShowing: " + mIsKeyguardShowing);
-        pw.println(" mIcon: ");
-        for (int state : mIcon.getState()) {
-            pw.print(" " + state);
-        }
         pw.println();
         pw.println(" mShowUnlockIcon: " + mShowUnlockIcon);
         pw.println(" mShowLockIcon: " + mShowLockIcon);
diff --git a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
index 2d2ebe9..d33d279 100644
--- a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
@@ -17,6 +17,7 @@
 package com.android.systemui
 
 import android.content.Context
+import android.content.res.Resources
 import android.graphics.Path
 import android.graphics.Rect
 import android.graphics.RectF
@@ -33,37 +34,32 @@
  */
 class CameraAvailabilityListener(
     private val cameraManager: CameraManager,
-    private val cutoutProtectionPath: Path,
-    private val targetCameraId: String,
+    private val cameraProtectionInfoList: List<CameraProtectionInfo>,
     excludedPackages: String,
     private val executor: Executor
 ) {
-    private var cutoutBounds = Rect()
     private val excludedPackageIds: Set<String>
     private val listeners = mutableListOf<CameraTransitionCallback>()
     private val availabilityCallback: CameraManager.AvailabilityCallback =
             object : CameraManager.AvailabilityCallback() {
                 override fun onCameraClosed(cameraId: String) {
-                    if (targetCameraId == cameraId) {
-                        notifyCameraInactive()
+                    cameraProtectionInfoList.forEach {
+                        if (cameraId == it.cameraId) {
+                            notifyCameraInactive()
+                        }
                     }
                 }
 
                 override fun onCameraOpened(cameraId: String, packageId: String) {
-                    if (targetCameraId == cameraId && !isExcluded(packageId)) {
-                        notifyCameraActive()
+                    cameraProtectionInfoList.forEach {
+                        if (cameraId == it.cameraId && !isExcluded(packageId)) {
+                            notifyCameraActive(it)
+                        }
                     }
                 }
     }
 
     init {
-        val computed = RectF()
-        cutoutProtectionPath.computeBounds(computed, false /* unused */)
-        cutoutBounds.set(
-                computed.left.roundToInt(),
-                computed.top.roundToInt(),
-                computed.right.roundToInt(),
-                computed.bottom.roundToInt())
         excludedPackageIds = excludedPackages.split(",").toSet()
     }
 
@@ -100,8 +96,10 @@
         cameraManager.unregisterAvailabilityCallback(availabilityCallback)
     }
 
-    private fun notifyCameraActive() {
-        listeners.forEach { it.onApplyCameraProtection(cutoutProtectionPath, cutoutBounds) }
+    private fun notifyCameraActive(info: CameraProtectionInfo) {
+        listeners.forEach {
+            it.onApplyCameraProtection(info.cutoutProtectionPath, info.cutoutBounds)
+        }
     }
 
     private fun notifyCameraInactive() {
@@ -121,12 +119,11 @@
             val manager = context
                     .getSystemService(Context.CAMERA_SERVICE) as CameraManager
             val res = context.resources
-            val pathString = res.getString(R.string.config_frontBuiltInDisplayCutoutProtection)
-            val cameraId = res.getString(R.string.config_protectedCameraId)
+            val cameraProtectionInfoList = loadCameraProtectionInfoList(res)
             val excluded = res.getString(R.string.config_cameraProtectionExcludedPackages)
 
             return CameraAvailabilityListener(
-                    manager, pathFromString(pathString), cameraId, excluded, executor)
+                    manager, cameraProtectionInfoList, excluded, executor)
         }
 
         private fun pathFromString(pathString: String): Path {
@@ -140,5 +137,53 @@
 
             return p
         }
+
+        private fun loadCameraProtectionInfoList(res: Resources): List<CameraProtectionInfo> {
+            val list = mutableListOf<CameraProtectionInfo>()
+            val front = loadCameraProtectionInfo(
+                    res,
+                    R.string.config_protectedCameraId,
+                    R.string.config_frontBuiltInDisplayCutoutProtection
+            )
+            if (front != null) {
+                list.add(front)
+            }
+            val inner = loadCameraProtectionInfo(
+                    res,
+                    R.string.config_protectedInnerCameraId,
+                    R.string.config_innerBuiltInDisplayCutoutProtection
+            )
+            if (inner != null) {
+                list.add(inner)
+            }
+            return list
+        }
+
+        private fun loadCameraProtectionInfo(
+                res: Resources,
+                cameraIdRes: Int,
+                pathRes: Int
+        ): CameraProtectionInfo? {
+            val cameraId = res.getString(cameraIdRes)
+            if (cameraId == null || cameraId.isEmpty()) {
+                return null
+            }
+            val protectionPath = pathFromString(res.getString(pathRes))
+            val computed = RectF()
+            protectionPath.computeBounds(computed)
+            val protectionBounds = Rect(
+                    computed.left.roundToInt(),
+                    computed.top.roundToInt(),
+                    computed.right.roundToInt(),
+                    computed.bottom.roundToInt()
+            )
+            return CameraProtectionInfo(cameraId, protectionPath, protectionBounds)
+        }
     }
+
+    data class CameraProtectionInfo (
+            val cameraId: String,
+            val cutoutProtectionPath: Path,
+            val cutoutBounds: Rect
+    )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java
index fd84543..494efb7 100644
--- a/packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java
@@ -25,21 +25,24 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.UserInfo;
+import android.content.res.Resources;
 import android.os.UserHandle;
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.qs.QSUserSwitcherEvent;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 
-import javax.inject.Inject;
-
+import dagger.Lazy;
 import dagger.assisted.Assisted;
 import dagger.assisted.AssistedFactory;
 import dagger.assisted.AssistedInject;
 
+import javax.inject.Inject;
+
 /**
  * Manages handling of guest session persistent notification
  * and actions to reset guest or exit guest session
@@ -70,14 +73,14 @@
     public AlertDialog mResetSessionDialog;
     private final UserTracker mUserTracker;
     private final BroadcastDispatcher mBroadcastDispatcher;
-    private final ResetSessionDialog.Factory mResetSessionDialogFactory;
-    private final ExitSessionDialog.Factory mExitSessionDialogFactory;
+    private final ResetSessionDialogFactory mResetSessionDialogFactory;
+    private final ExitSessionDialogFactory mExitSessionDialogFactory;
 
     @Inject
     public GuestResetOrExitSessionReceiver(UserTracker userTracker,
             BroadcastDispatcher broadcastDispatcher,
-            ResetSessionDialog.Factory resetSessionDialogFactory,
-            ExitSessionDialog.Factory exitSessionDialogFactory) {
+            ResetSessionDialogFactory resetSessionDialogFactory,
+            ExitSessionDialogFactory exitSessionDialogFactory) {
         mUserTracker = userTracker;
         mBroadcastDispatcher = broadcastDispatcher;
         mResetSessionDialogFactory = resetSessionDialogFactory;
@@ -111,8 +114,8 @@
             mResetSessionDialog = mResetSessionDialogFactory.create(currentUser.id);
             mResetSessionDialog.show();
         } else if (ACTION_GUEST_EXIT.equals(action)) {
-            mExitSessionDialog = mExitSessionDialogFactory.create(currentUser.id,
-                        currentUser.isEphemeral());
+            mExitSessionDialog = mExitSessionDialogFactory.create(
+                    currentUser.isEphemeral(), currentUser.id);
             mExitSessionDialog.show();
         }
     }
@@ -132,43 +135,69 @@
     }
 
     /**
+     * Factory class to create guest reset dialog instance
+     *
      * Dialog shown when asking for confirmation before
      * reset and restart of guest user.
      */
-    public static final class ResetSessionDialog extends SystemUIDialog implements
-            DialogInterface.OnClickListener {
+    public static final class ResetSessionDialogFactory {
+        private final Lazy<SystemUIDialog> mDialogLazy;
+        private final Resources mResources;
+        private final ResetSessionDialogClickListener.Factory mClickListenerFactory;
 
+        @Inject
+        public ResetSessionDialogFactory(
+                Lazy<SystemUIDialog> dialogLazy,
+                @Main Resources resources,
+                ResetSessionDialogClickListener.Factory clickListenerFactory) {
+            mDialogLazy = dialogLazy;
+            mResources = resources;
+            mClickListenerFactory = clickListenerFactory;
+        }
+
+        /** Create a guest reset dialog instance */
+        public AlertDialog create(int userId) {
+            SystemUIDialog dialog = mDialogLazy.get();
+            ResetSessionDialogClickListener listener = mClickListenerFactory.create(
+                    userId, dialog);
+            dialog.setTitle(com.android.settingslib.R.string.guest_reset_and_restart_dialog_title);
+            dialog.setMessage(mResources.getString(
+                    com.android.settingslib.R.string.guest_reset_and_restart_dialog_message));
+            dialog.setButton(
+                    DialogInterface.BUTTON_NEUTRAL,
+                    mResources.getString(android.R.string.cancel),
+                    listener);
+            dialog.setButton(DialogInterface.BUTTON_POSITIVE,
+                    mResources.getString(
+                            com.android.settingslib.R.string.guest_reset_guest_confirm_button),
+                    listener);
+            dialog.setCanceledOnTouchOutside(false);
+            return dialog;
+        }
+    }
+
+    public static class ResetSessionDialogClickListener implements DialogInterface.OnClickListener {
         private final UserSwitcherController mUserSwitcherController;
         private final UiEventLogger mUiEventLogger;
         private final int mUserId;
+        private final DialogInterface mDialog;
 
-        /** Factory class to create guest reset dialog instance */
         @AssistedFactory
         public interface Factory {
-            /** Create a guest reset dialog instance */
-            ResetSessionDialog create(int userId);
+            ResetSessionDialogClickListener create(int userId, DialogInterface dialog);
         }
 
         @AssistedInject
-        ResetSessionDialog(Context context,
+        public ResetSessionDialogClickListener(
                 UserSwitcherController userSwitcherController,
                 UiEventLogger uiEventLogger,
-                @Assisted int userId) {
-            super(context);
-
-            setTitle(com.android.settingslib.R.string.guest_reset_and_restart_dialog_title);
-            setMessage(context.getString(
-                        com.android.settingslib.R.string.guest_reset_and_restart_dialog_message));
-            setButton(DialogInterface.BUTTON_NEUTRAL,
-                    context.getString(android.R.string.cancel), this);
-            setButton(DialogInterface.BUTTON_POSITIVE,
-                    context.getString(
-                        com.android.settingslib.R.string.guest_reset_guest_confirm_button), this);
-            setCanceledOnTouchOutside(false);
-
+                @Assisted int userId,
+                @Assisted DialogInterface dialog
+        ) {
             mUserSwitcherController = userSwitcherController;
             mUiEventLogger = uiEventLogger;
             mUserId = userId;
+            mDialog = dialog;
         }
 
         @Override
@@ -177,7 +206,7 @@
                 mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE);
                 mUserSwitcherController.removeGuestUser(mUserId, UserHandle.USER_NULL);
             } else if (which == DialogInterface.BUTTON_NEUTRAL) {
-                cancel();
+                mDialog.cancel();
             }
         }
     }
@@ -186,58 +215,93 @@
      * Dialog shown when asking for confirmation before
      * exit of guest user.
      */
-    public static final class ExitSessionDialog extends SystemUIDialog implements
-            DialogInterface.OnClickListener {
+    public static final class ExitSessionDialogFactory {
+        private final Lazy<SystemUIDialog> mDialogLazy;
+        private final ExitSessionDialogClickListener.Factory mClickListenerFactory;
+        private final Resources mResources;
 
+        @Inject
+        public ExitSessionDialogFactory(
+                Lazy<SystemUIDialog> dialogLazy,
+                ExitSessionDialogClickListener.Factory clickListenerFactory,
+                @Main Resources resources) {
+            mDialogLazy = dialogLazy;
+            mClickListenerFactory = clickListenerFactory;
+            mResources = resources;
+        }
+
+        public AlertDialog create(boolean isEphemeral, int userId) {
+            SystemUIDialog dialog = mDialogLazy.get();
+            ExitSessionDialogClickListener clickListener = mClickListenerFactory.create(
+                    isEphemeral, userId, dialog);
+            if (isEphemeral) {
+                dialog.setTitle(mResources.getString(
+                        com.android.settingslib.R.string.guest_exit_dialog_title));
+                dialog.setMessage(mResources.getString(
+                        com.android.settingslib.R.string.guest_exit_dialog_message));
+                dialog.setButton(
+                        DialogInterface.BUTTON_NEUTRAL,
+                        mResources.getString(android.R.string.cancel),
+                        clickListener);
+                dialog.setButton(
+                        DialogInterface.BUTTON_POSITIVE,
+                        mResources.getString(
+                                com.android.settingslib.R.string.guest_exit_dialog_button),
+                        clickListener);
+            } else {
+                dialog.setTitle(mResources.getString(
+                        com.android.settingslib
+                                .R.string.guest_exit_dialog_title_non_ephemeral));
+                dialog.setMessage(mResources.getString(
+                        com.android.settingslib
+                                .R.string.guest_exit_dialog_message_non_ephemeral));
+                dialog.setButton(
+                        DialogInterface.BUTTON_NEUTRAL,
+                        mResources.getString(android.R.string.cancel),
+                        clickListener);
+                dialog.setButton(
+                        DialogInterface.BUTTON_NEGATIVE,
+                        mResources.getString(
+                                com.android.settingslib.R.string.guest_exit_clear_data_button),
+                        clickListener);
+                dialog.setButton(
+                        DialogInterface.BUTTON_POSITIVE,
+                        mResources.getString(
+                                com.android.settingslib.R.string.guest_exit_save_data_button),
+                        clickListener);
+            }
+            dialog.setCanceledOnTouchOutside(false);
+
+            return dialog;
+        }
+
+    }
+
+    public static class ExitSessionDialogClickListener implements DialogInterface.OnClickListener {
         private final UserSwitcherController mUserSwitcherController;
+        private final boolean mIsEphemeral;
         private final int mUserId;
-        private boolean mIsEphemeral;
+        private final DialogInterface mDialog;
 
-        /** Factory class to create guest exit dialog instance */
         @AssistedFactory
         public interface Factory {
-            /** Create a guest exit dialog instance */
-            ExitSessionDialog create(int userId, boolean isEphemeral);
+            ExitSessionDialogClickListener create(
+                    boolean isEphemeral,
+                    int userId,
+                    DialogInterface dialog);
         }
 
         @AssistedInject
-        ExitSessionDialog(Context context,
+        public ExitSessionDialogClickListener(
                 UserSwitcherController userSwitcherController,
+                @Assisted boolean isEphemeral,
                 @Assisted int userId,
-                @Assisted boolean isEphemeral) {
-            super(context);
-
-            if (isEphemeral) {
-                setTitle(context.getString(
-                            com.android.settingslib.R.string.guest_exit_dialog_title));
-                setMessage(context.getString(
-                            com.android.settingslib.R.string.guest_exit_dialog_message));
-                setButton(DialogInterface.BUTTON_NEUTRAL,
-                        context.getString(android.R.string.cancel), this);
-                setButton(DialogInterface.BUTTON_POSITIVE,
-                        context.getString(
-                            com.android.settingslib.R.string.guest_exit_dialog_button), this);
-            } else {
-                setTitle(context.getString(
-                            com.android.settingslib
-                                .R.string.guest_exit_dialog_title_non_ephemeral));
-                setMessage(context.getString(
-                            com.android.settingslib
-                                .R.string.guest_exit_dialog_message_non_ephemeral));
-                setButton(DialogInterface.BUTTON_NEUTRAL,
-                        context.getString(android.R.string.cancel), this);
-                setButton(DialogInterface.BUTTON_NEGATIVE,
-                        context.getString(
-                            com.android.settingslib.R.string.guest_exit_clear_data_button), this);
-                setButton(DialogInterface.BUTTON_POSITIVE,
-                        context.getString(
-                            com.android.settingslib.R.string.guest_exit_save_data_button), this);
-            }
-            setCanceledOnTouchOutside(false);
-
+                @Assisted DialogInterface dialog
+        ) {
             mUserSwitcherController = userSwitcherController;
-            mUserId = userId;
             mIsEphemeral = isEphemeral;
+            mUserId = userId;
+            mDialog = dialog;
         }
 
         @Override
@@ -249,7 +313,7 @@
                     mUserSwitcherController.exitGuestUser(mUserId, UserHandle.USER_NULL, false);
                 } else if (which == DialogInterface.BUTTON_NEUTRAL) {
                     // Cancel clicked, do nothing
-                    cancel();
+                    mDialog.cancel();
                 }
             } else {
                 if (which == DialogInterface.BUTTON_POSITIVE) {
@@ -261,7 +325,7 @@
                     mUserSwitcherController.exitGuestUser(mUserId, UserHandle.USER_NULL, true);
                 } else if (which == DialogInterface.BUTTON_NEUTRAL) {
                     // Cancel clicked, do nothing
-                    cancel();
+                    mDialog.cancel();
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
index b573fad..0f5f869 100644
--- a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
@@ -27,6 +27,7 @@
 import com.android.systemui.res.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.GuestResetOrExitSessionReceiver.ResetSessionDialogFactory;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -36,14 +37,14 @@
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.util.settings.SecureSettings;
 
-import java.util.concurrent.Executor;
-
-import javax.inject.Inject;
-
 import dagger.assisted.Assisted;
 import dagger.assisted.AssistedFactory;
 import dagger.assisted.AssistedInject;
 
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
 /**
  * Manages notification when a guest session is resumed.
  */
@@ -58,7 +59,7 @@
     private final Executor mMainExecutor;
     private final UserTracker mUserTracker;
     private final SecureSettings mSecureSettings;
-    private final ResetSessionDialog.Factory mResetSessionDialogFactory;
+    private final ResetSessionDialogFactory mResetSessionDialogFactory;
     private final GuestSessionNotification mGuestSessionNotification;
 
     @VisibleForTesting
@@ -104,7 +105,7 @@
             UserTracker userTracker,
             SecureSettings secureSettings,
             GuestSessionNotification guestSessionNotification,
-            ResetSessionDialog.Factory resetSessionDialogFactory) {
+            ResetSessionDialogFactory resetSessionDialogFactory) {
         mMainExecutor = mainExecutor;
         mUserTracker = userTracker;
         mSecureSettings = secureSettings;
diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
index b33d501..c860979 100644
--- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java
+++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
@@ -31,7 +31,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.phone.BiometricUnlockController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
@@ -53,25 +53,25 @@
     private static final String
             ACTION_FACE_WAKE =
             "com.android.systemui.latency.ACTION_FACE_WAKE";
-    private final BiometricUnlockController mBiometricUnlockController;
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final DeviceConfigProxy mDeviceConfigProxy;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final SelectedUserInteractor mSelectedUserInteractor;
 
     private boolean mEnabled;
 
     @Inject
     public LatencyTester(
-            BiometricUnlockController biometricUnlockController,
             BroadcastDispatcher broadcastDispatcher,
             DeviceConfigProxy deviceConfigProxy,
             @Main DelayableExecutor mainExecutor,
-            KeyguardUpdateMonitor keyguardUpdateMonitor
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            SelectedUserInteractor selectedUserInteractor
     ) {
-        mBiometricUnlockController = biometricUnlockController;
         mBroadcastDispatcher = broadcastDispatcher;
         mDeviceConfigProxy = deviceConfigProxy;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mSelectedUserInteractor = selectedUserInteractor;
 
         updateEnabled();
         mDeviceConfigProxy.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
@@ -87,11 +87,11 @@
             return;
         }
         if (type == BiometricSourceType.FACE) {
-            mKeyguardUpdateMonitor.onFaceAuthenticated(KeyguardUpdateMonitor.getCurrentUser(),
+            mKeyguardUpdateMonitor.onFaceAuthenticated(mSelectedUserInteractor.getSelectedUserId(),
                     true);
         } else if (type == BiometricSourceType.FINGERPRINT) {
             mKeyguardUpdateMonitor.onFingerprintAuthenticated(
-                    KeyguardUpdateMonitor.getCurrentUser(), true);
+                    mSelectedUserInteractor.getSelectedUserId(), true);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index 0e339dd..9305ab6 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -28,17 +28,17 @@
 import com.android.internal.app.IVoiceInteractionSessionListener;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.res.R;
 import com.android.systemui.assist.ui.DefaultUiController;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.settings.SecureSettings;
 
 import dagger.Lazy;
@@ -144,6 +144,7 @@
     private final UserTracker mUserTracker;
     private final DisplayTracker mDisplayTracker;
     private final SecureSettings mSecureSettings;
+    private final SelectedUserInteractor mSelectedUserInteractor;
 
     private final DeviceProvisionedController mDeviceProvisionedController;
 
@@ -152,16 +153,16 @@
 
     private final IVisualQueryDetectionAttentionListener mVisualQueryDetectionAttentionListener =
             new IVisualQueryDetectionAttentionListener.Stub() {
-        @Override
-        public void onAttentionGained() {
-            handleVisualAttentionChanged(true);
-        }
+                @Override
+                public void onAttentionGained() {
+                    handleVisualAttentionChanged(true);
+                }
 
-        @Override
-        public void onAttentionLost() {
-            handleVisualAttentionChanged(false);
-        }
-    };
+                @Override
+                public void onAttentionLost() {
+                    handleVisualAttentionChanged(false);
+                }
+            };
 
     private final CommandQueue mCommandQueue;
     protected final AssistUtils mAssistUtils;
@@ -183,7 +184,8 @@
             @Main Handler uiHandler,
             UserTracker userTracker,
             DisplayTracker displayTracker,
-            SecureSettings secureSettings) {
+            SecureSettings secureSettings,
+            SelectedUserInteractor selectedUserInteractor) {
         mContext = context;
         mDeviceProvisionedController = controller;
         mCommandQueue = commandQueue;
@@ -195,6 +197,7 @@
         mUserTracker = userTracker;
         mDisplayTracker = displayTracker;
         mSecureSettings = secureSettings;
+        mSelectedUserInteractor = selectedUserInteractor;
 
         registerVoiceInteractionSessionListener();
         registerVisualQueryRecognitionStatusListener();
@@ -316,12 +319,13 @@
     public boolean shouldOverrideAssist(int invocationType) {
         return mAssistOverrideInvocationTypes != null
                 && Arrays.stream(mAssistOverrideInvocationTypes).anyMatch(
-                        override -> override == invocationType);
+                    override -> override == invocationType);
     }
 
     /**
      * @param invocationTypes The invocation types that will henceforth be handled via
-     *         OverviewProxy (Launcher); other invocation types should be handled by this class.
+     *                        OverviewProxy (Launcher); other invocation types should be handled by
+     *                        this class.
      */
     public void setAssistantOverridesRequested(int[] invocationTypes) {
         mAssistOverrideInvocationTypes = invocationTypes;
@@ -478,7 +482,7 @@
 
     @Nullable
     private ComponentName getAssistInfo() {
-        return getAssistInfoForUser(KeyguardUpdateMonitor.getCurrentUser());
+        return getAssistInfoForUser(mSelectedUserInteractor.getSelectedUserId());
     }
 
     public void showDisclosure() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
deleted file mode 100644
index 3f2da5e..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.biometrics
-
-import android.content.Context
-import android.graphics.drawable.Drawable
-import android.util.Log
-import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.res.R
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
-
-private const val TAG = "AuthBiometricFaceIconController"
-
-/** Face only icon animator for BiometricPrompt. */
-class AuthBiometricFaceIconController(
-        context: Context,
-        iconView: LottieAnimationView
-) : AuthIconController(context, iconView) {
-
-    // false = dark to light, true = light to dark
-    private var lastPulseLightToDark = false
-
-    private var state: BiometricState = BiometricState.STATE_IDLE
-
-    init {
-        val size = context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
-        iconView.layoutParams.width = size
-        iconView.layoutParams.height = size
-        showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light)
-    }
-
-    private fun startPulsing() {
-        lastPulseLightToDark = false
-        animateIcon(R.drawable.face_dialog_pulse_dark_to_light, true)
-    }
-
-    private fun pulseInNextDirection() {
-        val iconRes = if (lastPulseLightToDark) {
-            R.drawable.face_dialog_pulse_dark_to_light
-        } else {
-            R.drawable.face_dialog_pulse_light_to_dark
-        }
-        animateIcon(iconRes, true /* repeat */)
-        lastPulseLightToDark = !lastPulseLightToDark
-    }
-
-    override fun handleAnimationEnd(drawable: Drawable) {
-        if (state == BiometricState.STATE_AUTHENTICATING || state == BiometricState.STATE_HELP) {
-            pulseInNextDirection()
-        }
-    }
-
-    override fun updateIcon(oldState: BiometricState, newState: BiometricState) {
-        val lastStateIsErrorIcon = (oldState == BiometricState.STATE_ERROR || oldState == BiometricState.STATE_HELP)
-        if (newState == BiometricState.STATE_AUTHENTICATING_ANIMATING_IN) {
-            showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light)
-            iconView.contentDescription = context.getString(
-                    R.string.biometric_dialog_face_icon_description_authenticating
-            )
-        } else if (newState == BiometricState.STATE_AUTHENTICATING) {
-            startPulsing()
-            iconView.contentDescription = context.getString(
-                    R.string.biometric_dialog_face_icon_description_authenticating
-            )
-        } else if (oldState == BiometricState.STATE_PENDING_CONFIRMATION && newState == BiometricState.STATE_AUTHENTICATED) {
-            animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
-            iconView.contentDescription = context.getString(
-                    R.string.biometric_dialog_face_icon_description_confirmed
-            )
-        } else if (lastStateIsErrorIcon && newState == BiometricState.STATE_IDLE) {
-            animateIconOnce(R.drawable.face_dialog_error_to_idle)
-            iconView.contentDescription = context.getString(
-                    R.string.biometric_dialog_face_icon_description_idle
-            )
-        } else if (lastStateIsErrorIcon && newState == BiometricState.STATE_AUTHENTICATED) {
-            animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
-            iconView.contentDescription = context.getString(
-                    R.string.biometric_dialog_face_icon_description_authenticated
-            )
-        } else if (newState == BiometricState.STATE_ERROR && oldState != BiometricState.STATE_ERROR) {
-            animateIconOnce(R.drawable.face_dialog_dark_to_error)
-            iconView.contentDescription = context.getString(
-                    R.string.keyguard_face_failed
-            )
-        } else if (oldState == BiometricState.STATE_AUTHENTICATING && newState == BiometricState.STATE_AUTHENTICATED) {
-            animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
-            iconView.contentDescription = context.getString(
-                    R.string.biometric_dialog_face_icon_description_authenticated
-            )
-        } else if (newState == BiometricState.STATE_PENDING_CONFIRMATION) {
-            animateIconOnce(R.drawable.face_dialog_wink_from_dark)
-            iconView.contentDescription = context.getString(
-                    R.string.biometric_dialog_face_icon_description_authenticated
-            )
-        } else if (newState == BiometricState.STATE_IDLE) {
-            showStaticDrawable(R.drawable.face_dialog_idle_static)
-            iconView.contentDescription = context.getString(
-                    R.string.biometric_dialog_face_icon_description_idle
-            )
-        } else {
-            Log.w(TAG, "Unhandled state: $newState")
-        }
-        state = newState
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
deleted file mode 100644
index 09eabf2..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics
-
-import android.annotation.RawRes
-import android.content.Context
-import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.res.R
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATED
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_ERROR
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_HELP
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION
-
-/** Face/Fingerprint combined icon animator for BiometricPrompt. */
-open class AuthBiometricFingerprintAndFaceIconController(
-    context: Context,
-    iconView: LottieAnimationView,
-    iconViewOverlay: LottieAnimationView,
-) : AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay) {
-
-    override val actsAsConfirmButton: Boolean = true
-
-    override fun shouldAnimateIconViewForTransition(
-            oldState: BiometricState,
-            newState: BiometricState
-    ): Boolean = when (newState) {
-        STATE_PENDING_CONFIRMATION -> true
-        else -> super.shouldAnimateIconViewForTransition(oldState, newState)
-    }
-
-    @RawRes
-    override fun getAnimationForTransition(
-        oldState: BiometricState,
-        newState: BiometricState
-    ): Int? = when (newState) {
-        STATE_AUTHENTICATED -> {
-           if (oldState == STATE_PENDING_CONFIRMATION) {
-               R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie
-           } else {
-               super.getAnimationForTransition(oldState, newState)
-           }
-        }
-        STATE_PENDING_CONFIRMATION -> {
-            if (oldState == STATE_ERROR || oldState == STATE_HELP) {
-                R.raw.fingerprint_dialogue_error_to_unlock_lottie
-            } else {
-                R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie
-            }
-        }
-        else -> super.getAnimationForTransition(oldState, newState)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
deleted file mode 100644
index 0ad3848..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ /dev/null
@@ -1,342 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics
-
-import android.annotation.RawRes
-import android.content.Context
-import android.content.Context.FINGERPRINT_SERVICE
-import android.hardware.fingerprint.FingerprintManager
-import android.view.DisplayInfo
-import android.view.Surface
-import android.view.View
-import androidx.annotation.VisibleForTesting
-import com.airbnb.lottie.LottieAnimationView
-import com.android.settingslib.widget.LottieColorUtils
-import com.android.systemui.res.R
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATED
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATING
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATING_ANIMATING_IN
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_ERROR
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_HELP
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_IDLE
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION
-
-
-/** Fingerprint only icon animator for BiometricPrompt.  */
-open class AuthBiometricFingerprintIconController(
-        context: Context,
-        iconView: LottieAnimationView,
-        protected val iconViewOverlay: LottieAnimationView
-) : AuthIconController(context, iconView) {
-
-    private val isSideFps: Boolean
-    private val isReverseDefaultRotation =
-            context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
-
-    var iconLayoutParamSize: Pair<Int, Int> = Pair(1, 1)
-        set(value) {
-            if (field == value) {
-                return
-            }
-            iconViewOverlay.layoutParams.width = value.first
-            iconViewOverlay.layoutParams.height = value.second
-            iconView.layoutParams.width = value.first
-            iconView.layoutParams.height = value.second
-            field = value
-        }
-
-    init {
-        iconLayoutParamSize = Pair(context.resources.getDimensionPixelSize(
-                R.dimen.biometric_dialog_fingerprint_icon_width),
-                context.resources.getDimensionPixelSize(
-                        R.dimen.biometric_dialog_fingerprint_icon_height))
-        isSideFps =
-            (context.getSystemService(FINGERPRINT_SERVICE) as FingerprintManager?)?.let { fpm ->
-                fpm.sensorPropertiesInternal.any { it.isAnySidefpsType }
-            } ?: false
-        preloadAssets(context)
-        val displayInfo = DisplayInfo()
-        context.display?.getDisplayInfo(displayInfo)
-        if (isSideFps && getRotationFromDefault(displayInfo.rotation) == Surface.ROTATION_180) {
-            iconView.rotation = 180f
-        }
-    }
-
-    private fun updateIconSideFps(lastState: BiometricState, newState: BiometricState) {
-        val displayInfo = DisplayInfo()
-        context.display?.getDisplayInfo(displayInfo)
-        val rotation = getRotationFromDefault(displayInfo.rotation)
-        val iconViewOverlayAnimation =
-                getSideFpsOverlayAnimationForTransition(lastState, newState, rotation) ?: return
-
-        if (!(lastState == STATE_AUTHENTICATING_ANIMATING_IN && newState == STATE_AUTHENTICATING)) {
-            iconViewOverlay.setAnimation(iconViewOverlayAnimation)
-        }
-
-        val iconContentDescription = getIconContentDescription(newState)
-        if (iconContentDescription != null) {
-            iconView.contentDescription = iconContentDescription
-        }
-
-        iconView.frame = 0
-        iconViewOverlay.frame = 0
-        if (shouldAnimateSfpsIconViewForTransition(lastState, newState)) {
-            iconView.playAnimation()
-        }
-
-        if (shouldAnimateIconViewOverlayForTransition(lastState, newState)) {
-            iconViewOverlay.playAnimation()
-        }
-
-        LottieColorUtils.applyDynamicColors(context, iconView)
-        LottieColorUtils.applyDynamicColors(context, iconViewOverlay)
-    }
-
-    private fun updateIconNormal(lastState: BiometricState, newState: BiometricState) {
-        val icon = getAnimationForTransition(lastState, newState) ?: return
-
-        if (!(lastState == STATE_AUTHENTICATING_ANIMATING_IN && newState == STATE_AUTHENTICATING)) {
-            iconView.setAnimation(icon)
-        }
-
-        val iconContentDescription = getIconContentDescription(newState)
-        if (iconContentDescription != null) {
-            iconView.contentDescription = iconContentDescription
-        }
-
-        iconView.frame = 0
-        if (shouldAnimateIconViewForTransition(lastState, newState)) {
-            iconView.playAnimation()
-        }
-        LottieColorUtils.applyDynamicColors(context, iconView)
-    }
-
-    override fun updateIcon(lastState: BiometricState, newState: BiometricState) {
-        if (isSideFps) {
-            updateIconSideFps(lastState, newState)
-        } else {
-            iconViewOverlay.visibility = View.GONE
-            updateIconNormal(lastState, newState)
-        }
-    }
-
-    @VisibleForTesting
-    fun getIconContentDescription(newState: BiometricState): CharSequence? {
-        val id = when (newState) {
-            STATE_IDLE,
-            STATE_AUTHENTICATING_ANIMATING_IN,
-            STATE_AUTHENTICATING,
-            STATE_AUTHENTICATED ->
-                if (isSideFps) {
-                    R.string.security_settings_sfps_enroll_find_sensor_message
-                } else {
-                    R.string.fingerprint_dialog_touch_sensor
-                }
-            STATE_PENDING_CONFIRMATION ->
-                if (isSideFps) {
-                    R.string.security_settings_sfps_enroll_find_sensor_message
-                } else {
-                    R.string.fingerprint_dialog_authenticated_confirmation
-                }
-            STATE_ERROR,
-            STATE_HELP -> R.string.biometric_dialog_try_again
-            else -> null
-        }
-        return if (id != null) context.getString(id) else null
-    }
-
-    protected open fun shouldAnimateIconViewForTransition(
-            oldState: BiometricState,
-            newState: BiometricState
-    ) = when (newState) {
-        STATE_HELP,
-        STATE_ERROR -> true
-        STATE_AUTHENTICATING_ANIMATING_IN,
-        STATE_AUTHENTICATING -> oldState == STATE_ERROR || oldState == STATE_HELP
-        STATE_AUTHENTICATED -> true
-        else -> false
-    }
-
-    private fun shouldAnimateSfpsIconViewForTransition(
-            oldState: BiometricState,
-            newState: BiometricState
-    ) = when (newState) {
-        STATE_HELP,
-        STATE_ERROR -> true
-        STATE_AUTHENTICATING_ANIMATING_IN,
-        STATE_AUTHENTICATING ->
-            oldState == STATE_ERROR || oldState == STATE_HELP || oldState == STATE_IDLE
-        STATE_AUTHENTICATED -> true
-        else -> false
-    }
-
-    protected open fun shouldAnimateIconViewOverlayForTransition(
-            oldState: BiometricState,
-            newState: BiometricState
-    ) = when (newState) {
-        STATE_HELP,
-        STATE_ERROR -> true
-        STATE_AUTHENTICATING_ANIMATING_IN,
-        STATE_AUTHENTICATING -> oldState == STATE_ERROR || oldState == STATE_HELP
-        STATE_AUTHENTICATED -> true
-        else -> false
-    }
-
-    @RawRes
-    protected open fun getAnimationForTransition(
-            oldState: BiometricState,
-            newState: BiometricState
-    ): Int? {
-        val id = when (newState) {
-            STATE_HELP,
-            STATE_ERROR -> {
-                R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
-            }
-            STATE_AUTHENTICATING_ANIMATING_IN,
-            STATE_AUTHENTICATING -> {
-                if (oldState == STATE_ERROR || oldState == STATE_HELP) {
-                    R.raw.fingerprint_dialogue_error_to_fingerprint_lottie
-                } else {
-                    R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
-                }
-            }
-            STATE_AUTHENTICATED -> {
-                if (oldState == STATE_ERROR || oldState == STATE_HELP) {
-                    R.raw.fingerprint_dialogue_error_to_success_lottie
-                } else {
-                    R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
-                }
-            }
-            else -> return null
-        }
-        return if (id != null) return id else null
-    }
-
-    private fun getRotationFromDefault(rotation: Int): Int =
-            if (isReverseDefaultRotation) (rotation + 1) % 4 else rotation
-
-    @RawRes
-    private fun getSideFpsOverlayAnimationForTransition(
-            oldState: BiometricState,
-            newState: BiometricState,
-            rotation: Int
-    ): Int? = when (newState) {
-        STATE_HELP,
-        STATE_ERROR -> {
-            when (rotation) {
-                Surface.ROTATION_0 -> R.raw.biometricprompt_fingerprint_to_error_landscape
-                Surface.ROTATION_90 ->
-                    R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
-                Surface.ROTATION_180 ->
-                    R.raw.biometricprompt_fingerprint_to_error_landscape
-                Surface.ROTATION_270 ->
-                    R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
-                else -> R.raw.biometricprompt_fingerprint_to_error_landscape
-            }
-        }
-        STATE_AUTHENTICATING_ANIMATING_IN,
-        STATE_AUTHENTICATING -> {
-            if (oldState == STATE_ERROR || oldState == STATE_HELP) {
-                when (rotation) {
-                    Surface.ROTATION_0 ->
-                        R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
-                    Surface.ROTATION_90 ->
-                        R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft
-                    Surface.ROTATION_180 ->
-                        R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
-                    Surface.ROTATION_270 ->
-                        R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright
-                    else -> R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
-                }
-            } else {
-                when (rotation) {
-                    Surface.ROTATION_0 -> R.raw.biometricprompt_fingerprint_to_error_landscape
-                    Surface.ROTATION_90 ->
-                        R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
-                    Surface.ROTATION_180 ->
-                        R.raw.biometricprompt_fingerprint_to_error_landscape
-                    Surface.ROTATION_270 ->
-                        R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
-                    else -> R.raw.biometricprompt_fingerprint_to_error_landscape
-                }
-            }
-        }
-        STATE_AUTHENTICATED -> {
-            if (oldState == STATE_ERROR || oldState == STATE_HELP) {
-                when (rotation) {
-                    Surface.ROTATION_0 ->
-                        R.raw.biometricprompt_symbol_error_to_success_landscape
-                    Surface.ROTATION_90 ->
-                        R.raw.biometricprompt_symbol_error_to_success_portrait_topleft
-                    Surface.ROTATION_180 ->
-                        R.raw.biometricprompt_symbol_error_to_success_landscape
-                    Surface.ROTATION_270 ->
-                        R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright
-                    else -> R.raw.biometricprompt_symbol_error_to_success_landscape
-                }
-            } else {
-                when (rotation) {
-                    Surface.ROTATION_0 ->
-                        R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
-                    Surface.ROTATION_90 ->
-                        R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
-                    Surface.ROTATION_180 ->
-                        R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
-                    Surface.ROTATION_270 ->
-                        R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright
-                    else -> R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
-                }
-            }
-        }
-        else -> null
-    }
-
-    private fun preloadAssets(context: Context) {
-        if (isSideFps) {
-            cacheLottieAssetsInContext(
-                context,
-                R.raw.biometricprompt_fingerprint_to_error_landscape,
-                R.raw.biometricprompt_folded_base_bottomright,
-                R.raw.biometricprompt_folded_base_default,
-                R.raw.biometricprompt_folded_base_topleft,
-                R.raw.biometricprompt_landscape_base,
-                R.raw.biometricprompt_portrait_base_bottomright,
-                R.raw.biometricprompt_portrait_base_topleft,
-                R.raw.biometricprompt_symbol_error_to_fingerprint_landscape,
-                R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright,
-                R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft,
-                R.raw.biometricprompt_symbol_error_to_success_landscape,
-                R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright,
-                R.raw.biometricprompt_symbol_error_to_success_portrait_topleft,
-                R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright,
-                R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft,
-                R.raw.biometricprompt_symbol_fingerprint_to_success_landscape,
-                R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright,
-                R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
-            )
-        } else {
-            cacheLottieAssetsInContext(
-                context,
-                R.raw.fingerprint_dialogue_error_to_fingerprint_lottie,
-                R.raw.fingerprint_dialogue_error_to_success_lottie,
-                R.raw.fingerprint_dialogue_fingerprint_to_error_lottie,
-                R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
-            )
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
index 054bd08..8d1d905 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
@@ -1,3 +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 com.android.systemui.biometrics
 
 import android.annotation.MainThread
@@ -25,7 +41,7 @@
             shadeExpansionCollectorJob =
                 scope.launch {
                     // wait for it to emit true once
-                    shadeInteractorLazy.get().isAnyExpanding.first { it }
+                    shadeInteractorLazy.get().isUserInteracting.first { it }
                     onShadeInteraction.run()
                 }
             shadeExpansionCollectorJob?.invokeOnCompletion { shadeExpansionCollectorJob = null }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt
deleted file mode 100644
index 958213a..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics
-
-import android.annotation.DrawableRes
-import android.content.Context
-import android.graphics.drawable.Animatable2
-import android.graphics.drawable.AnimatedVectorDrawable
-import android.graphics.drawable.Drawable
-import android.util.Log
-import com.airbnb.lottie.LottieAnimationView
-import com.airbnb.lottie.LottieCompositionFactory
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
-
-private const val TAG = "AuthIconController"
-
-/** Controller for animating the BiometricPrompt icon/affordance. */
-abstract class AuthIconController(
-    protected val context: Context,
-    protected val iconView: LottieAnimationView
-) : Animatable2.AnimationCallback() {
-
-    /** If this controller should ignore events and pause. */
-    var deactivated: Boolean = false
-
-    /** If the icon view should be treated as an alternate "confirm" button. */
-    open val actsAsConfirmButton: Boolean = false
-
-    final override fun onAnimationStart(drawable: Drawable) {
-        super.onAnimationStart(drawable)
-    }
-
-    final override fun onAnimationEnd(drawable: Drawable) {
-        super.onAnimationEnd(drawable)
-
-        if (!deactivated) {
-            handleAnimationEnd(drawable)
-        }
-    }
-
-    /** Set the icon to a static image. */
-    protected fun showStaticDrawable(@DrawableRes iconRes: Int) {
-        iconView.setImageDrawable(context.getDrawable(iconRes))
-    }
-
-    /** Animate a resource. */
-    protected fun animateIconOnce(@DrawableRes iconRes: Int) {
-        animateIcon(iconRes, false)
-    }
-
-    /** Animate a resource. */
-    protected fun animateIcon(@DrawableRes iconRes: Int, repeat: Boolean) {
-        if (!deactivated) {
-            val icon = context.getDrawable(iconRes) as AnimatedVectorDrawable
-            iconView.setImageDrawable(icon)
-            icon.forceAnimationOnUI()
-            if (repeat) {
-                icon.registerAnimationCallback(this)
-            }
-            icon.start()
-        }
-    }
-
-    /** Update the icon to reflect the [newState]. */
-    fun updateState(lastState: BiometricState, newState: BiometricState) {
-        if (deactivated) {
-            Log.w(TAG, "Ignoring updateState when deactivated: $newState")
-        } else {
-            updateIcon(lastState, newState)
-        }
-    }
-
-    /** Call during [updateState] if the controller is not [deactivated]. */
-    abstract fun updateIcon(lastState: BiometricState, newState: BiometricState)
-
-    /** Called during [onAnimationEnd] if the controller is not [deactivated]. */
-    open fun handleAnimationEnd(drawable: Drawable) {}
-
-    // TODO(b/251476085): Migrate this to an extension at the appropriate level?
-    /** Load the given [rawResources] immediately so they are cached for use in the [context]. */
-    protected fun cacheLottieAssetsInContext(context: Context, vararg rawResources: Int) {
-        for (res in rawResources) {
-            LottieCompositionFactory.fromRawRes(context, res)
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index c9e4cbe..92eacf1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -97,6 +97,7 @@
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.concurrency.Execution;
 import com.android.systemui.util.time.SystemClock;
@@ -167,6 +168,7 @@
     @NonNull private final AlternateBouncerInteractor mAlternateBouncerInteractor;
     @NonNull private final InputManager mInputManager;
     @NonNull private final UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
+    @NonNull private final SelectedUserInteractor mSelectedUserInteractor;
     private final boolean mIgnoreRefreshRate;
 
     // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
@@ -281,7 +283,8 @@
                         mPrimaryBouncerInteractor,
                         mAlternateBouncerInteractor,
                         mUdfpsKeyguardAccessibilityDelegate,
-                        mUdfpsKeyguardViewModels
+                        mUdfpsKeyguardViewModels,
+                            mSelectedUserInteractor
                     )));
         }
 
@@ -644,7 +647,8 @@
             @NonNull InputManager inputManager,
             @NonNull KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
             @NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate,
-            @NonNull Provider<UdfpsKeyguardViewModels> udfpsKeyguardViewModelsProvider) {
+            @NonNull Provider<UdfpsKeyguardViewModels> udfpsKeyguardViewModelsProvider,
+            @NonNull SelectedUserInteractor selectedUserInteractor) {
         mContext = context;
         mExecution = execution;
         mVibrator = vibrator;
@@ -687,6 +691,7 @@
         mAlternateBouncerInteractor = alternateBouncerInteractor;
         mInputManager = inputManager;
         mUdfpsKeyguardAccessibilityDelegate = udfpsKeyguardAccessibilityDelegate;
+        mSelectedUserInteractor = selectedUserInteractor;
 
         mTouchProcessor = singlePointerTouchProcessor;
         mSessionTracker = sessionTracker;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 7130bfb..272e0f2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -44,7 +44,6 @@
 import androidx.annotation.LayoutRes
 import androidx.annotation.VisibleForTesting
 import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.res.R
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.biometrics.ui.controller.UdfpsKeyguardViewController
@@ -56,12 +55,14 @@
 import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
 import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import javax.inject.Provider
 
@@ -78,31 +79,32 @@
 @ExperimentalCoroutinesApi
 @UiThread
 class UdfpsControllerOverlay @JvmOverloads constructor(
-        private val context: Context,
-        private val inflater: LayoutInflater,
-        private val windowManager: WindowManager,
-        private val accessibilityManager: AccessibilityManager,
-        private val statusBarStateController: StatusBarStateController,
-        private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
-        private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
-        private val dialogManager: SystemUIDialogManager,
-        private val dumpManager: DumpManager,
-        private val transitionController: LockscreenShadeTransitionController,
-        private val configurationController: ConfigurationController,
-        private val keyguardStateController: KeyguardStateController,
-        private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
-        private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider,
-        val requestId: Long,
-        @ShowReason val requestReason: Int,
-        private val controllerCallback: IUdfpsOverlayControllerCallback,
-        private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
-        private val activityLaunchAnimator: ActivityLaunchAnimator,
-        private val featureFlags: FeatureFlags,
-        private val primaryBouncerInteractor: PrimaryBouncerInteractor,
-        private val alternateBouncerInteractor: AlternateBouncerInteractor,
-        private val isDebuggable: Boolean = Build.IS_DEBUGGABLE,
-        private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
-        private val udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels>,
+    private val context: Context,
+    private val inflater: LayoutInflater,
+    private val windowManager: WindowManager,
+    private val accessibilityManager: AccessibilityManager,
+    private val statusBarStateController: StatusBarStateController,
+    private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    private val dialogManager: SystemUIDialogManager,
+    private val dumpManager: DumpManager,
+    private val transitionController: LockscreenShadeTransitionController,
+    private val configurationController: ConfigurationController,
+    private val keyguardStateController: KeyguardStateController,
+    private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
+    private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider,
+    val requestId: Long,
+    @ShowReason val requestReason: Int,
+    private val controllerCallback: IUdfpsOverlayControllerCallback,
+    private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
+    private val activityLaunchAnimator: ActivityLaunchAnimator,
+    private val featureFlags: FeatureFlags,
+    private val primaryBouncerInteractor: PrimaryBouncerInteractor,
+    private val alternateBouncerInteractor: AlternateBouncerInteractor,
+    private val isDebuggable: Boolean = Build.IS_DEBUGGABLE,
+    private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
+    private val udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels>,
+    private val selectedUserInteractor: SelectedUserInteractor,
 ) {
     /** The view, when [isShowing], or null. */
     var overlayView: UdfpsView? = null
@@ -268,6 +270,7 @@
                         primaryBouncerInteractor,
                         alternateBouncerInteractor,
                         udfpsKeyguardAccessibilityDelegate,
+                        selectedUserInteractor,
                     )
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index 3d5be6f..d7df0e5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -45,6 +45,7 @@
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import java.io.PrintWriter
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
@@ -68,6 +69,7 @@
     primaryBouncerInteractor: PrimaryBouncerInteractor,
     private val alternateBouncerInteractor: AlternateBouncerInteractor,
     private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
+    private val selectedUserInteractor: SelectedUserInteractor,
 ) :
     UdfpsAnimationViewController<UdfpsKeyguardViewLegacy>(
         view,
@@ -384,7 +386,7 @@
         }
         if (
             keyguardUpdateMonitor.getUserUnlockedWithBiometric(
-                KeyguardUpdateMonitor.getCurrentUser()
+                selectedUserInteractor.getSelectedUserId()
             )
         ) {
             // If the device was unlocked by a biometric, immediately hide the UDFPS icon to avoid
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
index c4c52e8b..050b399 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
@@ -42,8 +42,11 @@
 /** Repository for the current state of the display */
 interface DisplayStateRepository {
     /**
-     * Whether or not the direction rotation is applied to get to an application's requested
-     * orientation is reversed.
+     * If true, the direction rotation is applied to get to an application's requested orientation
+     * is reversed. Normally, the model is that landscape is clockwise from portrait; thus on a
+     * portrait device an app requesting landscape will cause a clockwise rotation, and on a
+     * landscape device an app requesting portrait will cause a counter-clockwise rotation. Setting
+     * true here reverses that logic. See go/natural-orientation for context.
      */
     val isReverseDefaultRotation: Boolean
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
index a317a06..427361d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
@@ -57,6 +57,15 @@
     /** Display change event indicating a change to the given displayId has occurred. */
     val displayChanges: Flow<Int>
 
+    /**
+     * If true, the direction rotation is applied to get to an application's requested orientation
+     * is reversed. Normally, the model is that landscape is clockwise from portrait; thus on a
+     * portrait device an app requesting landscape will cause a clockwise rotation, and on a
+     * landscape device an app requesting portrait will cause a counter-clockwise rotation. Setting
+     * true here reverses that logic. See go/natural-orientation for context.
+     */
+    val isReverseDefaultRotation: Boolean
+
     /** Called on configuration changes, used to keep the display state in sync */
     fun onConfigurationChanged(newConfig: Configuration)
 }
@@ -112,6 +121,8 @@
     override val currentRotation: StateFlow<DisplayRotation> =
         displayStateRepository.currentRotation
 
+    override val isReverseDefaultRotation: Boolean = displayStateRepository.isReverseDefaultRotation
+
     override fun onConfigurationChanged(newConfig: Configuration) {
         screenSizeFoldProvider.onConfigurationChange(newConfig)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
index 2a1047a..38043b4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
@@ -17,13 +17,13 @@
 package com.android.systemui.biometrics.domain.interactor
 
 import android.view.MotionEvent
-import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
@@ -35,11 +35,16 @@
 @SysUISingleton
 class UdfpsOverlayInteractor
 @Inject
-constructor(private val authController: AuthController, @Application scope: CoroutineScope) {
+constructor(
+    private val authController: AuthController,
+    private val selectedUserInteractor: SelectedUserInteractor,
+    @Application scope: CoroutineScope
+) {
 
     /** Whether a touch is within the under-display fingerprint sensor area */
     fun isTouchWithinUdfpsArea(ev: MotionEvent): Boolean {
-        val isUdfpsEnrolled = authController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())
+        val isUdfpsEnrolled =
+            authController.isUdfpsEnrolled(selectedUserInteractor.getSelectedUserId())
         val isWithinOverlayBounds =
             udfpsOverlayParams.value.overlayBounds.contains(ev.rawX.toInt(), ev.rawY.toInt())
         return isUdfpsEnrolled && isWithinOverlayBounds
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
index cef0be0..0d72b9c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
@@ -29,11 +29,10 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
-import com.android.systemui.res.R;
-import com.android.systemui.biometrics.AuthBiometricFingerprintIconController;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.AuthDialog;
 import com.android.systemui.biometrics.UdfpsDialogMeasureAdapter;
+import com.android.systemui.res.R;
 
 import kotlin.Pair;
 
@@ -85,13 +84,13 @@
     }
 
     @Deprecated
-    public void updateFingerprintAffordanceSize(
-            @NonNull AuthBiometricFingerprintIconController iconController) {
+    public Pair<Integer, Integer> getUpdatedFingerprintAffordanceSize() {
         if (mUdfpsAdapter != null) {
             final int sensorDiameter = mUdfpsAdapter.getSensorDiameter(
                     mScaleFactorProvider.provide());
-            iconController.setIconLayoutParamSize(new Pair(sensorDiameter, sensorDiameter));
+            return new Pair(sensorDiameter, sensorDiameter);
         }
+        return null;
     }
 
     @NonNull
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index c29efc0..ac48b6a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -38,11 +38,7 @@
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
 import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.res.R
-import com.android.systemui.biometrics.AuthBiometricFaceIconController
-import com.android.systemui.biometrics.AuthBiometricFingerprintAndFaceIconController
-import com.android.systemui.biometrics.AuthBiometricFingerprintIconController
-import com.android.systemui.biometrics.AuthIconController
+import com.airbnb.lottie.LottieCompositionFactory
 import com.android.systemui.biometrics.AuthPanelController
 import com.android.systemui.biometrics.shared.model.BiometricModalities
 import com.android.systemui.biometrics.shared.model.BiometricModality
@@ -56,6 +52,7 @@
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.VibratorHelper
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
@@ -101,10 +98,15 @@
             !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
         descriptionView.movementMethod = ScrollingMovementMethod()
 
-        val iconViewOverlay = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
+        val iconOverlayView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
         val iconView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon)
 
-        PromptFingerprintIconViewBinder.bind(iconView, viewModel.fingerprintIconViewModel)
+        PromptIconViewBinder.bind(
+            iconView,
+            iconOverlayView,
+            view.getUpdatedFingerprintAffordanceSize(),
+            viewModel.iconViewModel
+        )
 
         val indicatorMessageView = view.requireViewById<TextView>(R.id.indicator)
 
@@ -128,9 +130,21 @@
 
         // bind to prompt
         var boundSize = false
+
         view.repeatWhenAttached {
             // these do not change and need to be set before any size transitions
             val modalities = viewModel.modalities.first()
+            if (modalities.hasFingerprint) {
+                /**
+                 * Load the given [rawResources] immediately so they are cached for use in the
+                 * [context].
+                 */
+                val rawResources = viewModel.iconViewModel.getRawAssets(modalities.hasSfps)
+                for (res in rawResources) {
+                    LottieCompositionFactory.fromRawRes(view.context, res)
+                }
+            }
+
             titleView.text = viewModel.title.first()
             descriptionView.text = viewModel.description.first()
             subtitleView.text = viewModel.subtitle.first()
@@ -148,27 +162,8 @@
                 legacyCallback.onButtonTryAgain()
             }
 
-            // TODO(b/251476085): migrate legacy icon controllers and remove
-            var legacyState = viewModel.legacyState.value
-            val iconController =
-                modalities.asIconController(
-                    view.context,
-                    iconView,
-                    iconViewOverlay,
-                )
-            adapter.attach(this, iconController, modalities, legacyCallback)
-            if (iconController is AuthBiometricFingerprintIconController) {
-                view.updateFingerprintAffordanceSize(iconController)
-            }
-            if (iconController is HackyCoexIconController) {
-                iconController.faceMode = !viewModel.isConfirmationRequired.first()
-            }
+            adapter.attach(this, modalities, legacyCallback)
 
-            // the icon controller must be created before this happens for the legacy
-            // sizing code in BiometricPromptLayout to work correctly. Simplify this
-            // when those are also migrated. (otherwise the icon size may not be set to
-            // a pixel value before the view is measured and WRAP_CONTENT will be incorrectly
-            // used as part of the measure spec)
             if (!boundSize) {
                 boundSize = true
                 BiometricViewSizeBinder.bind(
@@ -212,14 +207,6 @@
                     ) {
                         legacyCallback.onStartDelayedFingerprintSensor()
                     }
-
-                    if (newMode.isStarted) {
-                        // do wonky switch from implicit to explicit flow
-                        (iconController as? HackyCoexIconController)?.faceMode = false
-                        viewModel.showAuthenticating(
-                            modalities.asDefaultHelpMessage(view.context),
-                        )
-                    }
                 }
             }
 
@@ -312,7 +299,7 @@
                     viewModel.isIconConfirmButton
                         .map { isPending ->
                             when {
-                                isPending && iconController.actsAsConfirmButton ->
+                                isPending && modalities.hasFaceAndFingerprint ->
                                     View.OnTouchListener { _: View, event: MotionEvent ->
                                         viewModel.onOverlayTouch(event)
                                     }
@@ -320,22 +307,11 @@
                             }
                         }
                         .collect { onTouch ->
-                            iconViewOverlay.setOnTouchListener(onTouch)
+                            iconOverlayView.setOnTouchListener(onTouch)
                             iconView.setOnTouchListener(onTouch)
                         }
                 }
 
-                // TODO(b/251476085): remove w/ legacy icon controllers
-                // set icon affordance using legacy states
-                // like the old code, this causes animations to repeat on config changes :(
-                // but keep behavior for now as no one has complained...
-                launch {
-                    viewModel.legacyState.collect { newState ->
-                        iconController.updateState(legacyState, newState)
-                        legacyState = newState
-                    }
-                }
-
                 // dismiss prompt when authenticated and confirmed
                 launch {
                     viewModel.isAuthenticated.collect { authState ->
@@ -350,7 +326,7 @@
 
                             // Allow icon to be used as confirmation button with a11y enabled
                             if (accessibilityManager.isTouchExplorationEnabled) {
-                                iconViewOverlay.setOnClickListener {
+                                iconOverlayView.setOnClickListener {
                                     viewModel.confirmAuthenticated()
                                 }
                                 iconView.setOnClickListener { viewModel.confirmAuthenticated() }
@@ -377,7 +353,6 @@
                 launch {
                     viewModel.message.collect { promptMessage ->
                         val isError = promptMessage is PromptMessage.Error
-
                         indicatorMessageView.text = promptMessage.message
                         indicatorMessageView.setTextColor(
                             if (isError) textColorError else textColorHint
@@ -472,9 +447,6 @@
     private var modalities: BiometricModalities = BiometricModalities()
     private var legacyCallback: Callback? = null
 
-    var legacyIconController: AuthIconController? = null
-        private set
-
     // hacky way to suppress lockout errors
     private val lockoutErrorStrings =
         listOf(
@@ -485,24 +457,20 @@
 
     fun attach(
         lifecycleOwner: LifecycleOwner,
-        iconController: AuthIconController,
         activeModalities: BiometricModalities,
         callback: Callback,
     ) {
         modalities = activeModalities
-        legacyIconController = iconController
         legacyCallback = callback
 
         lifecycleOwner.lifecycle.addObserver(
             object : DefaultLifecycleObserver {
                 override fun onCreate(owner: LifecycleOwner) {
                     lifecycleScope = owner.lifecycleScope
-                    iconController.deactivated = false
                 }
 
                 override fun onDestroy(owner: LifecycleOwner) {
                     lifecycleScope = null
-                    iconController.deactivated = true
                 }
             }
         )
@@ -626,61 +594,9 @@
         else -> ""
     }
 
-private fun BiometricModalities.asIconController(
-    context: Context,
-    iconView: LottieAnimationView,
-    iconViewOverlay: LottieAnimationView,
-): AuthIconController =
-    when {
-        hasFaceAndFingerprint -> HackyCoexIconController(context, iconView, iconViewOverlay)
-        hasFingerprint -> AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay)
-        hasFace -> AuthBiometricFaceIconController(context, iconView)
-        else -> throw IllegalStateException("unexpected view type :$this")
-    }
-
 private fun Boolean.asVisibleOrGone(): Int = if (this) View.VISIBLE else View.GONE
 
 private fun Boolean.asVisibleOrHidden(): Int = if (this) View.VISIBLE else View.INVISIBLE
 
 // TODO(b/251476085): proper type?
 typealias BiometricJankListener = Animator.AnimatorListener
-
-// TODO(b/251476085): delete - temporary until the legacy icon controllers are replaced
-private class HackyCoexIconController(
-    context: Context,
-    iconView: LottieAnimationView,
-    iconViewOverlay: LottieAnimationView,
-) : AuthBiometricFingerprintAndFaceIconController(context, iconView, iconViewOverlay) {
-
-    private var state: Spaghetti.BiometricState? = null
-    private val faceController = AuthBiometricFaceIconController(context, iconView)
-
-    var faceMode: Boolean = true
-        set(value) {
-            if (field != value) {
-                field = value
-
-                faceController.deactivated = !value
-                iconView.setImageIcon(null)
-                iconViewOverlay.setImageIcon(null)
-                state?.let { updateIcon(Spaghetti.BiometricState.STATE_IDLE, it) }
-            }
-        }
-
-    override fun updateIcon(
-        lastState: Spaghetti.BiometricState,
-        newState: Spaghetti.BiometricState,
-    ) {
-        if (deactivated) {
-            return
-        }
-
-        if (faceMode) {
-            faceController.updateIcon(lastState, newState)
-        } else {
-            super.updateIcon(lastState, newState)
-        }
-
-        state = newState
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptFingerprintIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptFingerprintIconViewBinder.kt
deleted file mode 100644
index d28f1dc..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptFingerprintIconViewBinder.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.biometrics.ui.binder
-
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
-import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.biometrics.ui.viewmodel.PromptFingerprintIconViewModel
-import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.launch
-
-/** Sub-binder for [BiometricPromptLayout.iconView]. */
-object PromptFingerprintIconViewBinder {
-
-    /** Binds [BiometricPromptLayout.iconView] to [PromptFingerprintIconViewModel]. */
-    @JvmStatic
-    fun bind(view: LottieAnimationView, viewModel: PromptFingerprintIconViewModel) {
-        view.repeatWhenAttached {
-            repeatOnLifecycle(Lifecycle.State.STARTED) {
-                viewModel.onConfigurationChanged(view.context.resources.configuration)
-                launch {
-                    viewModel.iconAsset.collect { iconAsset ->
-                        if (iconAsset != -1) {
-                            view.setAnimation(iconAsset)
-                            // TODO: must replace call below once non-sfps asset logic and
-                            // shouldAnimateIconView logic is migrated to this ViewModel.
-                            view.playAnimation()
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
new file mode 100644
index 0000000..475ef18
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
@@ -0,0 +1,200 @@
+/*
+ * 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.biometrics.ui.binder
+
+import android.graphics.drawable.Animatable2
+import android.graphics.drawable.AnimatedVectorDrawable
+import android.graphics.drawable.Drawable
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.airbnb.lottie.LottieAnimationView
+import com.android.settingslib.widget.LottieColorUtils
+import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel
+import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel.AuthType
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.util.kotlin.Utils.Companion.toQuad
+import com.android.systemui.util.kotlin.Utils.Companion.toQuint
+import com.android.systemui.util.kotlin.Utils.Companion.toTriple
+import com.android.systemui.util.kotlin.sample
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+
+/** Sub-binder for [BiometricPromptLayout.iconView]. */
+object PromptIconViewBinder {
+    /**
+     * Binds [BiometricPromptLayout.iconView] and [BiometricPromptLayout.biometric_icon_overlay] to
+     * [PromptIconViewModel].
+     */
+    @JvmStatic
+    fun bind(
+        iconView: LottieAnimationView,
+        iconOverlayView: LottieAnimationView,
+        iconViewLayoutParamSizeOverride: Pair<Int, Int>?,
+        viewModel: PromptIconViewModel
+    ) {
+        iconView.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                viewModel.onConfigurationChanged(iconView.context.resources.configuration)
+                if (iconViewLayoutParamSizeOverride != null) {
+                    iconView.layoutParams.width = iconViewLayoutParamSizeOverride.first
+                    iconView.layoutParams.height = iconViewLayoutParamSizeOverride.second
+
+                    iconOverlayView.layoutParams.width = iconViewLayoutParamSizeOverride.first
+                    iconOverlayView.layoutParams.height = iconViewLayoutParamSizeOverride.second
+                }
+
+                var faceIcon: AnimatedVectorDrawable? = null
+                val faceIconCallback =
+                    object : Animatable2.AnimationCallback() {
+                        override fun onAnimationStart(drawable: Drawable) {
+                            viewModel.onAnimationStart()
+                        }
+
+                        override fun onAnimationEnd(drawable: Drawable) {
+                            viewModel.onAnimationEnd()
+                        }
+                    }
+
+                launch {
+                    viewModel.activeAuthType.collect { activeAuthType ->
+                        if (iconViewLayoutParamSizeOverride == null) {
+                            val width: Int
+                            val height: Int
+                            when (activeAuthType) {
+                                AuthType.Fingerprint,
+                                AuthType.Coex -> {
+                                    width = viewModel.fingerprintIconWidth
+                                    height = viewModel.fingerprintIconHeight
+                                }
+                                AuthType.Face -> {
+                                    width = viewModel.faceIconWidth
+                                    height = viewModel.faceIconHeight
+                                }
+                            }
+
+                            iconView.layoutParams.width = width
+                            iconView.layoutParams.height = height
+
+                            iconOverlayView.layoutParams.width = width
+                            iconOverlayView.layoutParams.height = height
+                        }
+                    }
+                }
+
+                launch {
+                    viewModel.iconAsset
+                        .sample(
+                            combine(
+                                viewModel.activeAuthType,
+                                viewModel.shouldAnimateIconView,
+                                viewModel.shouldRepeatAnimation,
+                                viewModel.showingError,
+                                ::toQuad
+                            ),
+                            ::toQuint
+                        )
+                        .collect {
+                            (
+                                iconAsset,
+                                activeAuthType,
+                                shouldAnimateIconView,
+                                shouldRepeatAnimation,
+                                showingError) ->
+                            if (iconAsset != -1) {
+                                when (activeAuthType) {
+                                    AuthType.Fingerprint,
+                                    AuthType.Coex -> {
+                                        iconView.setAnimation(iconAsset)
+                                        iconView.frame = 0
+
+                                        if (shouldAnimateIconView) {
+                                            iconView.playAnimation()
+                                        }
+                                    }
+                                    AuthType.Face -> {
+                                        faceIcon?.apply {
+                                            unregisterAnimationCallback(faceIconCallback)
+                                            stop()
+                                        }
+                                        faceIcon =
+                                            iconView.context.getDrawable(iconAsset)
+                                                as AnimatedVectorDrawable
+                                        faceIcon?.apply {
+                                            iconView.setImageDrawable(this)
+                                            if (shouldAnimateIconView) {
+                                                forceAnimationOnUI()
+                                                if (shouldRepeatAnimation) {
+                                                    registerAnimationCallback(faceIconCallback)
+                                                }
+                                                start()
+                                            }
+                                        }
+                                    }
+                                }
+                                LottieColorUtils.applyDynamicColors(iconView.context, iconView)
+                                viewModel.setPreviousIconWasError(showingError)
+                            }
+                        }
+                }
+
+                launch {
+                    viewModel.iconOverlayAsset
+                        .sample(
+                            combine(
+                                viewModel.shouldAnimateIconOverlay,
+                                viewModel.showingError,
+                                ::Pair
+                            ),
+                            ::toTriple
+                        )
+                        .collect { (iconOverlayAsset, shouldAnimateIconOverlay, showingError) ->
+                            if (iconOverlayAsset != -1) {
+                                iconOverlayView.setAnimation(iconOverlayAsset)
+                                iconOverlayView.frame = 0
+                                LottieColorUtils.applyDynamicColors(
+                                    iconOverlayView.context,
+                                    iconOverlayView
+                                )
+
+                                if (shouldAnimateIconOverlay) {
+                                    iconOverlayView.playAnimation()
+                                }
+                                viewModel.setPreviousIconOverlayWasError(showingError)
+                            }
+                        }
+                }
+
+                launch {
+                    viewModel.shouldFlipIconView.collect { shouldFlipIconView ->
+                        if (shouldFlipIconView) {
+                            iconView.rotation = 180f
+                        }
+                    }
+                }
+
+                launch {
+                    viewModel.contentDescriptionId.collect { id ->
+                        if (id != -1) {
+                            iconView.contentDescription = iconView.context.getString(id)
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModel.kt
deleted file mode 100644
index dfd3a9b..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModel.kt
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.biometrics.ui.viewmodel
-
-import android.annotation.RawRes
-import android.content.res.Configuration
-import com.android.systemui.res.R
-import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
-import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
-import com.android.systemui.biometrics.shared.model.DisplayRotation
-import com.android.systemui.biometrics.shared.model.FingerprintSensorType
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-
-/** Models UI of [BiometricPromptLayout.iconView] */
-class PromptFingerprintIconViewModel
-@Inject
-constructor(
-    private val displayStateInteractor: DisplayStateInteractor,
-    promptSelectorInteractor: PromptSelectorInteractor,
-) {
-    /** Current BiometricPromptLayout.iconView asset. */
-    val iconAsset: Flow<Int> =
-        combine(
-            displayStateInteractor.currentRotation,
-            displayStateInteractor.isFolded,
-            displayStateInteractor.isInRearDisplayMode,
-            promptSelectorInteractor.sensorType,
-        ) {
-            rotation: DisplayRotation,
-            isFolded: Boolean,
-            isInRearDisplayMode: Boolean,
-            sensorType: FingerprintSensorType ->
-            when (sensorType) {
-                FingerprintSensorType.POWER_BUTTON ->
-                    getSideFpsAnimationAsset(rotation, isFolded, isInRearDisplayMode)
-                // Replace below when non-SFPS iconAsset logic is migrated to this ViewModel
-                else -> -1
-            }
-        }
-
-    @RawRes
-    private fun getSideFpsAnimationAsset(
-        rotation: DisplayRotation,
-        isDeviceFolded: Boolean,
-        isInRearDisplayMode: Boolean,
-    ): Int =
-        when (rotation) {
-            DisplayRotation.ROTATION_90 ->
-                if (isInRearDisplayMode) {
-                    R.raw.biometricprompt_rear_portrait_reverse_base
-                } else if (isDeviceFolded) {
-                    R.raw.biometricprompt_folded_base_topleft
-                } else {
-                    R.raw.biometricprompt_portrait_base_topleft
-                }
-            DisplayRotation.ROTATION_270 ->
-                if (isInRearDisplayMode) {
-                    R.raw.biometricprompt_rear_portrait_base
-                } else if (isDeviceFolded) {
-                    R.raw.biometricprompt_folded_base_bottomright
-                } else {
-                    R.raw.biometricprompt_portrait_base_bottomright
-                }
-            else ->
-                if (isInRearDisplayMode) {
-                    R.raw.biometricprompt_rear_landscape_base
-                } else if (isDeviceFolded) {
-                    R.raw.biometricprompt_folded_base_default
-                } else {
-                    R.raw.biometricprompt_landscape_base
-                }
-        }
-
-    /** Called on configuration changes */
-    fun onConfigurationChanged(newConfig: Configuration) {
-        displayStateInteractor.onConfigurationChanged(newConfig)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
new file mode 100644
index 0000000..11a5d8b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
@@ -0,0 +1,721 @@
+/*
+ * 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.biometrics.ui.viewmodel
+
+import android.annotation.DrawableRes
+import android.annotation.RawRes
+import android.content.res.Configuration
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
+import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
+import com.android.systemui.biometrics.shared.model.DisplayRotation
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.res.R
+import com.android.systemui.util.kotlin.combine
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+/**
+ * Models UI of [BiometricPromptLayout.iconView] and [BiometricPromptLayout.biometric_icon_overlay]
+ */
+class PromptIconViewModel
+constructor(
+    promptViewModel: PromptViewModel,
+    private val displayStateInteractor: DisplayStateInteractor,
+    promptSelectorInteractor: PromptSelectorInteractor
+) {
+
+    /** Auth types for the UI to display. */
+    enum class AuthType {
+        Fingerprint,
+        Face,
+        Coex
+    }
+
+    /**
+     * Indicates what auth type the UI currently displays.
+     * Fingerprint-only auth -> Fingerprint
+     * Face-only auth -> Face
+     * Co-ex auth, implicit flow -> Face
+     * Co-ex auth, explicit flow -> Coex
+     */
+    val activeAuthType: Flow<AuthType> =
+        combine(
+            promptViewModel.modalities.distinctUntilChanged(),
+            promptViewModel.faceMode.distinctUntilChanged()
+        ) { modalities, faceMode ->
+            if (modalities.hasFaceAndFingerprint && !faceMode) {
+                AuthType.Coex
+            } else if (modalities.hasFaceOnly || faceMode) {
+                AuthType.Face
+            } else if (modalities.hasFingerprintOnly) {
+                AuthType.Fingerprint
+            } else {
+                throw IllegalStateException("unexpected modality: $modalities")
+            }
+        }
+
+    /** Whether an error message is currently being shown. */
+    val showingError = promptViewModel.showingError
+
+    /** Whether the previous icon shown displayed an error. */
+    private val _previousIconWasError: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+    /** Whether the previous icon overlay shown displayed an error. */
+    private val _previousIconOverlayWasError: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+    fun setPreviousIconWasError(previousIconWasError: Boolean) {
+        _previousIconWasError.value = previousIconWasError
+    }
+
+    fun setPreviousIconOverlayWasError(previousIconOverlayWasError: Boolean) {
+        _previousIconOverlayWasError.value = previousIconOverlayWasError
+    }
+
+    /** Called when iconView begins animating. */
+    fun onAnimationStart() {
+        _animationEnded.value = false
+    }
+
+    /** Called when iconView ends animating. */
+    fun onAnimationEnd() {
+        _animationEnded.value = true
+    }
+
+    private val _animationEnded: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+    /**
+     * Whether a face iconView should pulse (i.e. while isAuthenticating and previous animation
+     * ended).
+     */
+    val shouldPulseAnimation: Flow<Boolean> =
+        combine(_animationEnded, promptViewModel.isAuthenticating) {
+                animationEnded,
+                isAuthenticating ->
+                animationEnded && isAuthenticating
+            }
+            .distinctUntilChanged()
+
+    private val _lastPulseLightToDark: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+    /** Tracks whether a face iconView last pulsed light to dark (vs. dark to light) */
+    val lastPulseLightToDark: Flow<Boolean> = _lastPulseLightToDark.asStateFlow()
+
+    /** Layout params for fingerprint iconView */
+    val fingerprintIconWidth: Int = promptViewModel.fingerprintIconWidth
+    val fingerprintIconHeight: Int = promptViewModel.fingerprintIconHeight
+
+    /** Layout params for face iconView */
+    val faceIconWidth: Int = promptViewModel.faceIconWidth
+    val faceIconHeight: Int = promptViewModel.faceIconHeight
+
+    /** Current BiometricPromptLayout.iconView asset. */
+    val iconAsset: Flow<Int> =
+        activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+            when (activeAuthType) {
+                AuthType.Fingerprint ->
+                    combine(
+                        displayStateInteractor.currentRotation,
+                        displayStateInteractor.isFolded,
+                        displayStateInteractor.isInRearDisplayMode,
+                        promptSelectorInteractor.sensorType,
+                        promptViewModel.isAuthenticated,
+                        promptViewModel.isAuthenticating,
+                        promptViewModel.showingError
+                    ) {
+                        rotation: DisplayRotation,
+                        isFolded: Boolean,
+                        isInRearDisplayMode: Boolean,
+                        sensorType: FingerprintSensorType,
+                        authState: PromptAuthState,
+                        isAuthenticating: Boolean,
+                        showingError: Boolean ->
+                        when (sensorType) {
+                            FingerprintSensorType.POWER_BUTTON ->
+                                getSfpsIconViewAsset(rotation, isFolded, isInRearDisplayMode)
+                            else ->
+                                getFingerprintIconViewAsset(
+                                    authState.isAuthenticated,
+                                    isAuthenticating,
+                                    showingError
+                                )
+                        }
+                    }
+                AuthType.Face ->
+                    shouldPulseAnimation.flatMapLatest { shouldPulseAnimation: Boolean ->
+                        if (shouldPulseAnimation) {
+                            val iconAsset =
+                                if (_lastPulseLightToDark.value) {
+                                    R.drawable.face_dialog_pulse_dark_to_light
+                                } else {
+                                    R.drawable.face_dialog_pulse_light_to_dark
+                                }
+                            _lastPulseLightToDark.value = !_lastPulseLightToDark.value
+                            flowOf(iconAsset)
+                        } else {
+                            combine(
+                                promptViewModel.isAuthenticated.distinctUntilChanged(),
+                                promptViewModel.isAuthenticating.distinctUntilChanged(),
+                                promptViewModel.isPendingConfirmation.distinctUntilChanged(),
+                                promptViewModel.showingError.distinctUntilChanged()
+                            ) {
+                                authState: PromptAuthState,
+                                isAuthenticating: Boolean,
+                                isPendingConfirmation: Boolean,
+                                showingError: Boolean ->
+                                getFaceIconViewAsset(
+                                    authState,
+                                    isAuthenticating,
+                                    isPendingConfirmation,
+                                    showingError
+                                )
+                            }
+                        }
+                    }
+                AuthType.Coex ->
+                    combine(
+                        displayStateInteractor.currentRotation,
+                        displayStateInteractor.isFolded,
+                        displayStateInteractor.isInRearDisplayMode,
+                        promptSelectorInteractor.sensorType,
+                        promptViewModel.isAuthenticated,
+                        promptViewModel.isAuthenticating,
+                        promptViewModel.isPendingConfirmation,
+                        promptViewModel.showingError,
+                    ) {
+                        rotation: DisplayRotation,
+                        isFolded: Boolean,
+                        isInRearDisplayMode: Boolean,
+                        sensorType: FingerprintSensorType,
+                        authState: PromptAuthState,
+                        isAuthenticating: Boolean,
+                        isPendingConfirmation: Boolean,
+                        showingError: Boolean ->
+                        when (sensorType) {
+                            FingerprintSensorType.POWER_BUTTON ->
+                                getSfpsIconViewAsset(rotation, isFolded, isInRearDisplayMode)
+                            else ->
+                                getCoexIconViewAsset(
+                                    authState,
+                                    isAuthenticating,
+                                    isPendingConfirmation,
+                                    showingError
+                                )
+                        }
+                    }
+            }
+        }
+
+    private fun getFingerprintIconViewAsset(
+        isAuthenticated: Boolean,
+        isAuthenticating: Boolean,
+        showingError: Boolean
+    ): Int =
+        if (isAuthenticated) {
+            if (_previousIconWasError.value) {
+                R.raw.fingerprint_dialogue_error_to_success_lottie
+            } else {
+                R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
+            }
+        } else if (isAuthenticating) {
+            if (_previousIconWasError.value) {
+                R.raw.fingerprint_dialogue_error_to_fingerprint_lottie
+            } else {
+                R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
+            }
+        } else if (showingError) {
+            R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
+        } else {
+            -1
+        }
+
+    @RawRes
+    private fun getSfpsIconViewAsset(
+        rotation: DisplayRotation,
+        isDeviceFolded: Boolean,
+        isInRearDisplayMode: Boolean,
+    ): Int =
+        when (rotation) {
+            DisplayRotation.ROTATION_90 ->
+                if (isInRearDisplayMode) {
+                    R.raw.biometricprompt_rear_portrait_reverse_base
+                } else if (isDeviceFolded) {
+                    R.raw.biometricprompt_folded_base_topleft
+                } else {
+                    R.raw.biometricprompt_portrait_base_topleft
+                }
+            DisplayRotation.ROTATION_270 ->
+                if (isInRearDisplayMode) {
+                    R.raw.biometricprompt_rear_portrait_base
+                } else if (isDeviceFolded) {
+                    R.raw.biometricprompt_folded_base_bottomright
+                } else {
+                    R.raw.biometricprompt_portrait_base_bottomright
+                }
+            else ->
+                if (isInRearDisplayMode) {
+                    R.raw.biometricprompt_rear_landscape_base
+                } else if (isDeviceFolded) {
+                    R.raw.biometricprompt_folded_base_default
+                } else {
+                    R.raw.biometricprompt_landscape_base
+                }
+        }
+
+    @DrawableRes
+    private fun getFaceIconViewAsset(
+        authState: PromptAuthState,
+        isAuthenticating: Boolean,
+        isPendingConfirmation: Boolean,
+        showingError: Boolean
+    ): Int =
+        if (authState.isAuthenticated && isPendingConfirmation) {
+            R.drawable.face_dialog_wink_from_dark
+        } else if (authState.isAuthenticated) {
+            R.drawable.face_dialog_dark_to_checkmark
+        } else if (isAuthenticating) {
+            _lastPulseLightToDark.value = false
+            R.drawable.face_dialog_pulse_dark_to_light
+        } else if (showingError) {
+            R.drawable.face_dialog_dark_to_error
+        } else if (_previousIconWasError.value) {
+            R.drawable.face_dialog_error_to_idle
+        } else {
+            R.drawable.face_dialog_idle_static
+        }
+
+    @RawRes
+    private fun getCoexIconViewAsset(
+        authState: PromptAuthState,
+        isAuthenticating: Boolean,
+        isPendingConfirmation: Boolean,
+        showingError: Boolean
+    ): Int =
+        if (authState.isAuthenticatedAndExplicitlyConfirmed) {
+            R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie
+        } else if (isPendingConfirmation) {
+            if (_previousIconWasError.value) {
+                R.raw.fingerprint_dialogue_error_to_unlock_lottie
+            } else {
+                R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie
+            }
+        } else if (authState.isAuthenticated) {
+            if (_previousIconWasError.value) {
+                R.raw.fingerprint_dialogue_error_to_success_lottie
+            } else {
+                R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
+            }
+        } else if (isAuthenticating) {
+            if (_previousIconWasError.value) {
+                R.raw.fingerprint_dialogue_error_to_fingerprint_lottie
+            } else {
+                R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
+            }
+        } else if (showingError) {
+            R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
+        } else {
+            -1
+        }
+
+    /** Current BiometricPromptLayout.biometric_icon_overlay asset. */
+    var iconOverlayAsset: Flow<Int> =
+        activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+            when (activeAuthType) {
+                AuthType.Fingerprint,
+                AuthType.Coex ->
+                    combine(
+                        displayStateInteractor.currentRotation,
+                        promptSelectorInteractor.sensorType,
+                        promptViewModel.isAuthenticated,
+                        promptViewModel.isAuthenticating,
+                        promptViewModel.showingError
+                    ) {
+                        rotation: DisplayRotation,
+                        sensorType: FingerprintSensorType,
+                        authState: PromptAuthState,
+                        isAuthenticating: Boolean,
+                        showingError: Boolean ->
+                        when (sensorType) {
+                            FingerprintSensorType.POWER_BUTTON ->
+                                getSfpsIconOverlayAsset(
+                                    rotation,
+                                    authState.isAuthenticated,
+                                    isAuthenticating,
+                                    showingError
+                                )
+                            else -> -1
+                        }
+                    }
+                AuthType.Face -> flowOf(-1)
+            }
+        }
+
+    @RawRes
+    private fun getSfpsIconOverlayAsset(
+        rotation: DisplayRotation,
+        isAuthenticated: Boolean,
+        isAuthenticating: Boolean,
+        showingError: Boolean
+    ): Int =
+        if (isAuthenticated) {
+            if (_previousIconOverlayWasError.value) {
+                when (rotation) {
+                    DisplayRotation.ROTATION_0 ->
+                        R.raw.biometricprompt_symbol_error_to_success_landscape
+                    DisplayRotation.ROTATION_90 ->
+                        R.raw.biometricprompt_symbol_error_to_success_portrait_topleft
+                    DisplayRotation.ROTATION_180 ->
+                        R.raw.biometricprompt_symbol_error_to_success_landscape
+                    DisplayRotation.ROTATION_270 ->
+                        R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright
+                }
+            } else {
+                when (rotation) {
+                    DisplayRotation.ROTATION_0 ->
+                        R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
+                    DisplayRotation.ROTATION_90 ->
+                        R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
+                    DisplayRotation.ROTATION_180 ->
+                        R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
+                    DisplayRotation.ROTATION_270 ->
+                        R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright
+                }
+            }
+        } else if (isAuthenticating) {
+            if (_previousIconOverlayWasError.value) {
+                when (rotation) {
+                    DisplayRotation.ROTATION_0 ->
+                        R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
+                    DisplayRotation.ROTATION_90 ->
+                        R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft
+                    DisplayRotation.ROTATION_180 ->
+                        R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
+                    DisplayRotation.ROTATION_270 ->
+                        R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright
+                }
+            } else {
+                when (rotation) {
+                    DisplayRotation.ROTATION_0 ->
+                        R.raw.biometricprompt_fingerprint_to_error_landscape
+                    DisplayRotation.ROTATION_90 ->
+                        R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
+                    DisplayRotation.ROTATION_180 ->
+                        R.raw.biometricprompt_fingerprint_to_error_landscape
+                    DisplayRotation.ROTATION_270 ->
+                        R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
+                }
+            }
+        } else if (showingError) {
+            when (rotation) {
+                DisplayRotation.ROTATION_0 -> R.raw.biometricprompt_fingerprint_to_error_landscape
+                DisplayRotation.ROTATION_90 ->
+                    R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
+                DisplayRotation.ROTATION_180 -> R.raw.biometricprompt_fingerprint_to_error_landscape
+                DisplayRotation.ROTATION_270 ->
+                    R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
+            }
+        } else {
+            -1
+        }
+
+    /** Content description for iconView */
+    val contentDescriptionId: Flow<Int> =
+        activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+            when (activeAuthType) {
+                AuthType.Fingerprint,
+                AuthType.Coex ->
+                    combine(
+                        promptSelectorInteractor.sensorType,
+                        promptViewModel.isAuthenticated,
+                        promptViewModel.isAuthenticating,
+                        promptViewModel.isPendingConfirmation,
+                        promptViewModel.showingError
+                    ) {
+                        sensorType: FingerprintSensorType,
+                        authState: PromptAuthState,
+                        isAuthenticating: Boolean,
+                        isPendingConfirmation: Boolean,
+                        showingError: Boolean ->
+                        getFingerprintIconContentDescriptionId(
+                            sensorType,
+                            authState.isAuthenticated,
+                            isAuthenticating,
+                            isPendingConfirmation,
+                            showingError
+                        )
+                    }
+                AuthType.Face ->
+                    combine(
+                        promptViewModel.isAuthenticated,
+                        promptViewModel.isAuthenticating,
+                        promptViewModel.showingError,
+                    ) { authState: PromptAuthState, isAuthenticating: Boolean, showingError: Boolean
+                        ->
+                        getFaceIconContentDescriptionId(authState, isAuthenticating, showingError)
+                    }
+            }
+        }
+
+    private fun getFingerprintIconContentDescriptionId(
+        sensorType: FingerprintSensorType,
+        isAuthenticated: Boolean,
+        isAuthenticating: Boolean,
+        isPendingConfirmation: Boolean,
+        showingError: Boolean
+    ): Int =
+        if (isPendingConfirmation) {
+            when (sensorType) {
+                FingerprintSensorType.POWER_BUTTON ->
+                    R.string.security_settings_sfps_enroll_find_sensor_message
+                else -> R.string.fingerprint_dialog_authenticated_confirmation
+            }
+        } else if (isAuthenticating || isAuthenticated) {
+            when (sensorType) {
+                FingerprintSensorType.POWER_BUTTON ->
+                    R.string.security_settings_sfps_enroll_find_sensor_message
+                else -> R.string.fingerprint_dialog_touch_sensor
+            }
+        } else if (showingError) {
+            R.string.biometric_dialog_try_again
+        } else {
+            -1
+        }
+
+    private fun getFaceIconContentDescriptionId(
+        authState: PromptAuthState,
+        isAuthenticating: Boolean,
+        showingError: Boolean
+    ): Int =
+        if (authState.isAuthenticatedAndExplicitlyConfirmed) {
+            R.string.biometric_dialog_face_icon_description_confirmed
+        } else if (authState.isAuthenticated) {
+            R.string.biometric_dialog_face_icon_description_authenticated
+        } else if (isAuthenticating) {
+            R.string.biometric_dialog_face_icon_description_authenticating
+        } else if (showingError) {
+            R.string.keyguard_face_failed
+        } else {
+            R.string.biometric_dialog_face_icon_description_idle
+        }
+
+    /** Whether the current BiometricPromptLayout.iconView asset animation should be playing. */
+    val shouldAnimateIconView: Flow<Boolean> =
+        activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+            when (activeAuthType) {
+                AuthType.Fingerprint ->
+                    combine(
+                        promptSelectorInteractor.sensorType,
+                        promptViewModel.isAuthenticated,
+                        promptViewModel.isAuthenticating,
+                        promptViewModel.showingError
+                    ) {
+                        sensorType: FingerprintSensorType,
+                        authState: PromptAuthState,
+                        isAuthenticating: Boolean,
+                        showingError: Boolean ->
+                        when (sensorType) {
+                            FingerprintSensorType.POWER_BUTTON ->
+                                shouldAnimateSfpsIconView(
+                                    authState.isAuthenticated,
+                                    isAuthenticating,
+                                    showingError
+                                )
+                            else ->
+                                shouldAnimateFingerprintIconView(
+                                    authState.isAuthenticated,
+                                    isAuthenticating,
+                                    showingError
+                                )
+                        }
+                    }
+                AuthType.Face ->
+                    combine(
+                        promptViewModel.isAuthenticated,
+                        promptViewModel.isAuthenticating,
+                        promptViewModel.showingError
+                    ) { authState: PromptAuthState, isAuthenticating: Boolean, showingError: Boolean
+                        ->
+                        isAuthenticating ||
+                            authState.isAuthenticated ||
+                            showingError ||
+                            _previousIconWasError.value
+                    }
+                AuthType.Coex ->
+                    combine(
+                        promptSelectorInteractor.sensorType,
+                        promptViewModel.isAuthenticated,
+                        promptViewModel.isAuthenticating,
+                        promptViewModel.isPendingConfirmation,
+                        promptViewModel.showingError,
+                    ) {
+                        sensorType: FingerprintSensorType,
+                        authState: PromptAuthState,
+                        isAuthenticating: Boolean,
+                        isPendingConfirmation: Boolean,
+                        showingError: Boolean ->
+                        when (sensorType) {
+                            FingerprintSensorType.POWER_BUTTON ->
+                                shouldAnimateSfpsIconView(
+                                    authState.isAuthenticated,
+                                    isAuthenticating,
+                                    showingError
+                                )
+                            else ->
+                                shouldAnimateCoexIconView(
+                                    authState.isAuthenticated,
+                                    isAuthenticating,
+                                    isPendingConfirmation,
+                                    showingError
+                                )
+                        }
+                    }
+            }
+        }
+
+    private fun shouldAnimateFingerprintIconView(
+        isAuthenticated: Boolean,
+        isAuthenticating: Boolean,
+        showingError: Boolean
+    ) = (isAuthenticating && _previousIconWasError.value) || isAuthenticated || showingError
+
+    private fun shouldAnimateSfpsIconView(
+        isAuthenticated: Boolean,
+        isAuthenticating: Boolean,
+        showingError: Boolean
+    ) = isAuthenticated || isAuthenticating || showingError
+
+    private fun shouldAnimateCoexIconView(
+        isAuthenticated: Boolean,
+        isAuthenticating: Boolean,
+        isPendingConfirmation: Boolean,
+        showingError: Boolean
+    ) =
+        (isAuthenticating && _previousIconWasError.value) ||
+            isPendingConfirmation ||
+            isAuthenticated ||
+            showingError
+
+    /** Whether the current iconOverlayAsset animation should be playing. */
+    val shouldAnimateIconOverlay: Flow<Boolean> =
+        activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+            when (activeAuthType) {
+                AuthType.Fingerprint,
+                AuthType.Coex ->
+                    combine(
+                        promptSelectorInteractor.sensorType,
+                        promptViewModel.isAuthenticated,
+                        promptViewModel.isAuthenticating,
+                        promptViewModel.showingError
+                    ) {
+                        sensorType: FingerprintSensorType,
+                        authState: PromptAuthState,
+                        isAuthenticating: Boolean,
+                        showingError: Boolean ->
+                        when (sensorType) {
+                            FingerprintSensorType.POWER_BUTTON ->
+                                shouldAnimateSfpsIconOverlay(
+                                    authState.isAuthenticated,
+                                    isAuthenticating,
+                                    showingError
+                                )
+                            else -> false
+                        }
+                    }
+                AuthType.Face -> flowOf(false)
+            }
+        }
+
+    private fun shouldAnimateSfpsIconOverlay(
+        isAuthenticated: Boolean,
+        isAuthenticating: Boolean,
+        showingError: Boolean
+    ) = (isAuthenticating && _previousIconOverlayWasError.value) || isAuthenticated || showingError
+
+    /** Whether the iconView should be flipped due to a device using reverse default rotation . */
+    val shouldFlipIconView: Flow<Boolean> =
+        activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+            when (activeAuthType) {
+                AuthType.Fingerprint,
+                AuthType.Coex ->
+                    combine(
+                        promptSelectorInteractor.sensorType,
+                        displayStateInteractor.currentRotation
+                    ) { sensorType: FingerprintSensorType, rotation: DisplayRotation ->
+                        when (sensorType) {
+                            FingerprintSensorType.POWER_BUTTON ->
+                                (rotation == DisplayRotation.ROTATION_180)
+                            else -> false
+                        }
+                    }
+                AuthType.Face -> flowOf(false)
+            }
+        }
+
+    /** Whether the current BiometricPromptLayout.iconView asset animation should be repeated. */
+    val shouldRepeatAnimation: Flow<Boolean> =
+        activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+            when (activeAuthType) {
+                AuthType.Fingerprint,
+                AuthType.Coex -> flowOf(false)
+                AuthType.Face -> promptViewModel.isAuthenticating.map { it }
+            }
+        }
+
+    /** Called on configuration changes */
+    fun onConfigurationChanged(newConfig: Configuration) {
+        displayStateInteractor.onConfigurationChanged(newConfig)
+    }
+
+    /** iconView assets for caching */
+    fun getRawAssets(hasSfps: Boolean): List<Int> {
+        return if (hasSfps) {
+            listOf(
+                R.raw.biometricprompt_fingerprint_to_error_landscape,
+                R.raw.biometricprompt_folded_base_bottomright,
+                R.raw.biometricprompt_folded_base_default,
+                R.raw.biometricprompt_folded_base_topleft,
+                R.raw.biometricprompt_landscape_base,
+                R.raw.biometricprompt_portrait_base_bottomright,
+                R.raw.biometricprompt_portrait_base_topleft,
+                R.raw.biometricprompt_symbol_error_to_fingerprint_landscape,
+                R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright,
+                R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft,
+                R.raw.biometricprompt_symbol_error_to_success_landscape,
+                R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright,
+                R.raw.biometricprompt_symbol_error_to_success_portrait_topleft,
+                R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright,
+                R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft,
+                R.raw.biometricprompt_symbol_fingerprint_to_success_landscape,
+                R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright,
+                R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
+            )
+        } else {
+            listOf(
+                R.raw.fingerprint_dialogue_error_to_fingerprint_lottie,
+                R.raw.fingerprint_dialogue_error_to_success_lottie,
+                R.raw.fingerprint_dialogue_fingerprint_to_error_lottie,
+                R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 267afae..e49b4a7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -28,10 +28,10 @@
 import com.android.systemui.biometrics.shared.model.BiometricModality
 import com.android.systemui.biometrics.shared.model.DisplayRotation
 import com.android.systemui.biometrics.shared.model.PromptKind
-import com.android.systemui.biometrics.ui.binder.Spaghetti
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.VibratorHelper
 import javax.inject.Inject
 import kotlinx.coroutines.Job
@@ -39,7 +39,6 @@
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
@@ -51,25 +50,29 @@
 class PromptViewModel
 @Inject
 constructor(
-    private val displayStateInteractor: DisplayStateInteractor,
-    private val promptSelectorInteractor: PromptSelectorInteractor,
+    displayStateInteractor: DisplayStateInteractor,
+    promptSelectorInteractor: PromptSelectorInteractor,
     private val vibrator: VibratorHelper,
     @Application context: Context,
     private val featureFlags: FeatureFlags,
 ) {
-    /** Models UI of [BiometricPromptLayout.iconView] */
-    val fingerprintIconViewModel: PromptFingerprintIconViewModel =
-        PromptFingerprintIconViewModel(displayStateInteractor, promptSelectorInteractor)
-
     /** The set of modalities available for this prompt */
     val modalities: Flow<BiometricModalities> =
         promptSelectorInteractor.prompt
             .map { it?.modalities ?: BiometricModalities() }
             .distinctUntilChanged()
 
-    // TODO(b/251476085): remove after icon controllers are migrated - do not keep this state
-    private var _legacyState = MutableStateFlow(Spaghetti.BiometricState.STATE_IDLE)
-    val legacyState: StateFlow<Spaghetti.BiometricState> = _legacyState.asStateFlow()
+    /** Layout params for fingerprint iconView */
+    val fingerprintIconWidth: Int =
+        context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_fingerprint_icon_width)
+    val fingerprintIconHeight: Int =
+        context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_fingerprint_icon_height)
+
+    /** Layout params for face iconView */
+    val faceIconWidth: Int =
+        context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
+    val faceIconHeight: Int =
+        context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
 
     private val _isAuthenticating: MutableStateFlow<Boolean> = MutableStateFlow(false)
 
@@ -82,6 +85,12 @@
     /** If the user has successfully authenticated and confirmed (when explicitly required). */
     val isAuthenticated: Flow<PromptAuthState> = _isAuthenticated.asStateFlow()
 
+    /** If the auth is pending confirmation. */
+    val isPendingConfirmation: Flow<Boolean> =
+        isAuthenticated.map { authState ->
+            authState.isAuthenticated && authState.needsUserConfirmation
+        }
+
     private val _isOverlayTouched: MutableStateFlow<Boolean> = MutableStateFlow(false)
 
     /** The kind of credential the user has. */
@@ -96,6 +105,9 @@
     /** A message to show the user, if there is an error, hint, or help to show. */
     val message: Flow<PromptMessage> = _message.asStateFlow()
 
+    /** Whether an error message is currently being shown. */
+    val showingError: Flow<Boolean> = message.map { it.isError }.distinctUntilChanged()
+
     private val isRetrySupported: Flow<Boolean> = modalities.map { it.hasFace }
 
     private val _fingerprintStartMode = MutableStateFlow(FingerprintStartMode.Pending)
@@ -141,6 +153,38 @@
             !isOverlayTouched && size.isNotSmall
         }
 
+    /**
+     * When fingerprint and face modalities are enrolled, indicates whether only face auth has
+     * started.
+     *
+     * True when fingerprint and face modalities are enrolled and implicit flow is active. This
+     * occurs in co-ex auth when confirmation is not required and only face auth is started, then
+     * becomes false when device transitions to explicit flow after a first error, when the
+     * fingerprint sensor is started.
+     *
+     * False when the dialog opens in explicit flow (fingerprint and face modalities enrolled but
+     * confirmation is required), or if user has only fingerprint enrolled, or only face enrolled.
+     */
+    val faceMode: Flow<Boolean> =
+        combine(modalities, isConfirmationRequired, fingerprintStartMode) {
+                modalities: BiometricModalities,
+                isConfirmationRequired: Boolean,
+                fingerprintStartMode: FingerprintStartMode ->
+                if (modalities.hasFaceAndFingerprint) {
+                    if (isConfirmationRequired) {
+                        false
+                    } else {
+                        !fingerprintStartMode.isStarted
+                    }
+                } else {
+                    false
+                }
+            }
+            .distinctUntilChanged()
+
+    val iconViewModel: PromptIconViewModel =
+        PromptIconViewModel(this, displayStateInteractor, promptSelectorInteractor)
+
     /** Padding for prompt UI elements */
     val promptPadding: Flow<Rect> =
         combine(size, displayStateInteractor.currentRotation) { size, rotation ->
@@ -184,9 +228,9 @@
     val isConfirmButtonVisible: Flow<Boolean> =
         combine(
                 size,
-                isAuthenticated,
-            ) { size, authState ->
-                size.isNotSmall && authState.isAuthenticated && authState.needsUserConfirmation
+                isPendingConfirmation,
+            ) { size, isPendingConfirmation ->
+                size.isNotSmall && isPendingConfirmation
             }
             .distinctUntilChanged()
 
@@ -293,7 +337,6 @@
         _isAuthenticated.value = PromptAuthState(false)
         _forceMediumSize.value = true
         _message.value = PromptMessage.Error(message)
-        _legacyState.value = Spaghetti.BiometricState.STATE_ERROR
 
         if (hapticFeedback) {
             vibrator.error(failedModality)
@@ -305,7 +348,7 @@
             if (authenticateAfterError) {
                 showAuthenticating(messageAfterError)
             } else {
-                showInfo(messageAfterError)
+                showHelp(messageAfterError)
             }
         }
     }
@@ -325,15 +368,12 @@
     private fun supportsRetry(failedModality: BiometricModality) =
         failedModality == BiometricModality.Face
 
-    suspend fun showHelp(message: String) = showHelp(message, clearIconError = false)
-    suspend fun showInfo(message: String) = showHelp(message, clearIconError = true)
-
     /**
      * Show a persistent help message.
      *
      * Will be show even if the user has already authenticated.
      */
-    private suspend fun showHelp(message: String, clearIconError: Boolean) {
+    suspend fun showHelp(message: String) {
         val alreadyAuthenticated = _isAuthenticated.value.isAuthenticated
         if (!alreadyAuthenticated) {
             _isAuthenticating.value = false
@@ -343,16 +383,6 @@
         _message.value =
             if (message.isNotBlank()) PromptMessage.Help(message) else PromptMessage.Empty
         _forceMediumSize.value = true
-        _legacyState.value =
-            if (alreadyAuthenticated && isConfirmationRequired.first()) {
-                Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION
-            } else if (alreadyAuthenticated && !isConfirmationRequired.first()) {
-                Spaghetti.BiometricState.STATE_AUTHENTICATED
-            } else if (clearIconError) {
-                Spaghetti.BiometricState.STATE_IDLE
-            } else {
-                Spaghetti.BiometricState.STATE_HELP
-            }
 
         messageJob?.cancel()
         messageJob = null
@@ -376,7 +406,6 @@
         _message.value =
             if (message.isNotBlank()) PromptMessage.Help(message) else PromptMessage.Empty
         _forceMediumSize.value = true
-        _legacyState.value = Spaghetti.BiometricState.STATE_HELP
 
         messageJob?.cancel()
         messageJob = launch {
@@ -396,7 +425,6 @@
         _isAuthenticating.value = true
         _isAuthenticated.value = PromptAuthState(false)
         _message.value = if (message.isBlank()) PromptMessage.Empty else PromptMessage.Help(message)
-        _legacyState.value = Spaghetti.BiometricState.STATE_AUTHENTICATING
 
         // reset the try again button(s) after the user attempts a retry
         if (isRetry) {
@@ -427,12 +455,6 @@
         _isAuthenticated.value =
             PromptAuthState(true, modality, needsUserConfirmation, dismissAfterDelay)
         _message.value = PromptMessage.Empty
-        _legacyState.value =
-            if (needsUserConfirmation) {
-                Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION
-            } else {
-                Spaghetti.BiometricState.STATE_AUTHENTICATED
-            }
 
         if (!needsUserConfirmation) {
             vibrator.success(modality)
@@ -472,7 +494,6 @@
 
         _isAuthenticated.value = authState.asExplicitlyConfirmed()
         _message.value = PromptMessage.Empty
-        _legacyState.value = Spaghetti.BiometricState.STATE_AUTHENTICATED
 
         vibrator.success(authState.authenticatedModality)
 
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
index 6e26fe9..21578f4 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
@@ -28,7 +28,6 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.DejankUtils
-import com.android.systemui.res.R
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
@@ -41,8 +40,10 @@
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.TrustRepository
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
 import com.android.systemui.shared.system.SysUiStatsLog
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
@@ -73,6 +74,7 @@
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val trustRepository: TrustRepository,
     @Application private val applicationScope: CoroutineScope,
+    private val selectedUserInteractor: SelectedUserInteractor,
 ) {
     private val passiveAuthBouncerDelay =
         context.resources.getInteger(R.integer.primary_bouncer_passive_auth_delay).toLong()
@@ -144,6 +146,16 @@
     /** Show the bouncer if necessary and set the relevant states. */
     @JvmOverloads
     fun show(isScrimmed: Boolean) {
+        if (primaryBouncerView.delegate == null) {
+            Log.d(
+                TAG,
+                "PrimaryBouncerInteractor#show is being called before the " +
+                    "primaryBouncerDelegate is set. Let's exit early so we don't set the wrong " +
+                    "primaryBouncer state."
+            )
+            return
+        }
+
         // Reset some states as we show the bouncer.
         repository.setKeyguardAuthenticatedBiometrics(null)
         repository.setPrimaryStartingToHide(false)
@@ -384,7 +396,7 @@
     /** Returns whether the bouncer should be full screen. */
     private fun needsFullscreenBouncer(): Boolean {
         val mode: KeyguardSecurityModel.SecurityMode =
-            keyguardSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser())
+            keyguardSecurityModel.getSecurityMode(selectedUserInteractor.getSelectedUserId())
         return mode == KeyguardSecurityModel.SecurityMode.SimPin ||
             mode == KeyguardSecurityModel.SecurityMode.SimPuk
     }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
index 36e5db4..ac3d4b6 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
@@ -26,7 +26,6 @@
 import com.android.keyguard.KeyguardSecurityContainerController
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardSecurityView
-import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.dagger.KeyguardBouncerComponent
 import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
@@ -37,6 +36,7 @@
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.log.BouncerLogger
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.launch
@@ -53,6 +53,7 @@
         bouncerMessageInteractor: BouncerMessageInteractor,
         bouncerLogger: BouncerLogger,
         featureFlags: FeatureFlags,
+        selectedUserInteractor: SelectedUserInteractor,
     ) {
         // Builds the KeyguardSecurityContainerController from bouncer view group.
         val securityContainerController: KeyguardSecurityContainerController =
@@ -84,7 +85,7 @@
 
                 override fun showNextSecurityScreenOrFinish(): Boolean {
                     return securityContainerController.dismiss(
-                        KeyguardUpdateMonitor.getCurrentUser()
+                        selectedUserInteractor.getSelectedUserId()
                     )
                 }
 
@@ -220,7 +221,7 @@
                     launch {
                         viewModel.keyguardAuthenticated.collect {
                             securityContainerController.finish(
-                                KeyguardUpdateMonitor.getCurrentUser()
+                                selectedUserInteractor.getSelectedUserId()
                             )
                             viewModel.notifyKeyguardAuthenticated()
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
index b268095..11c7a31 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
@@ -27,17 +27,16 @@
 import android.os.RemoteException
 import android.util.Log
 import android.view.WindowManager
-import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.ActivityIntentHelper
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserTracker
 import com.android.systemui.shared.system.ActivityManagerKt.isInForeground
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import java.util.concurrent.Executor
 import javax.inject.Inject
 
@@ -59,7 +58,7 @@
     private val cameraIntents: CameraIntentsWrapper,
     private val contentResolver: ContentResolver,
     @Main private val uiExecutor: Executor,
-    private val userTracker: UserTracker
+    private val selectedUserInteractor: SelectedUserInteractor,
 ) {
     /**
      * Whether the camera application can be launched for the camera launch gesture.
@@ -72,12 +71,12 @@
         val resolveInfo: ResolveInfo? = packageManager.resolveActivityAsUser(
             getStartCameraIntent(),
             PackageManager.MATCH_DEFAULT_ONLY,
-            KeyguardUpdateMonitor.getCurrentUser()
+            selectedUserInteractor.getSelectedUserId()
         )
         val resolvedPackage = resolveInfo?.activityInfo?.packageName
         return (resolvedPackage != null &&
                 (statusBarState != StatusBarState.SHADE ||
-                !activityManager.isInForeground(resolvedPackage)))
+                        !activityManager.isInForeground(resolvedPackage)))
     }
 
     /**
@@ -89,7 +88,7 @@
         val intent: Intent = getStartCameraIntent()
         intent.putExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, source)
         val wouldLaunchResolverActivity = activityIntentHelper.wouldLaunchResolverActivity(
-            intent, KeyguardUpdateMonitor.getCurrentUser()
+            intent, selectedUserInteractor.getSelectedUserId()
         )
         if (CameraIntents.isSecureCameraIntent(intent) && !wouldLaunchResolverActivity) {
             uiExecutor.execute {
@@ -102,7 +101,7 @@
                 val activityOptions = ActivityOptions.makeBasic()
                 activityOptions.setDisallowEnterPictureInPictureWhileLaunching(true)
                 activityOptions.rotationAnimationHint =
-                    WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS
+                        WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS
                 try {
                     activityTaskManager.startActivityAsUser(
                         null,
@@ -116,7 +115,7 @@
                         Intent.FLAG_ACTIVITY_NEW_TASK,
                         null,
                         activityOptions.toBundle(),
-                        userTracker.userId,
+                        selectedUserInteractor.getSelectedUserId(true),
                     )
                 } catch (e: RemoteException) {
                     Log.w(
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index 39c01f7..a6b073d 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -37,12 +37,15 @@
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.sensors.ProximitySensor;
 import com.android.systemui.util.sensors.ThresholdSensor;
 import com.android.systemui.util.sensors.ThresholdSensorEvent;
 import com.android.systemui.util.time.SystemClock;
 
+import dagger.Lazy;
+
 import java.util.Collections;
 
 import javax.inject.Inject;
@@ -66,6 +69,7 @@
     private final DockManager mDockManager;
     private final DelayableExecutor mMainExecutor;
     private final SystemClock mSystemClock;
+    private final Lazy<SelectedUserInteractor> mUserInteractor;
 
     private int mState;
     private boolean mShowingAod;
@@ -93,7 +97,7 @@
                 public void onBiometricAuthenticated(int userId,
                         BiometricSourceType biometricSourceType,
                         boolean isStrongBiometric) {
-                    if (userId == KeyguardUpdateMonitor.getCurrentUser()
+                    if (userId == mUserInteractor.get().getSelectedUserId()
                             && biometricSourceType == BiometricSourceType.FACE) {
                         mFalsingDataProvider.setJustUnlockedWithFace(true);
                     }
@@ -136,7 +140,8 @@
             BatteryController batteryController,
             DockManager dockManager,
             @Main DelayableExecutor mainExecutor,
-            SystemClock systemClock) {
+            SystemClock systemClock,
+            Lazy<SelectedUserInteractor> userInteractor) {
         mFalsingDataProvider = falsingDataProvider;
         mFalsingManager = falsingManager;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -148,6 +153,7 @@
         mDockManager = dockManager;
         mMainExecutor = mainExecutor;
         mSystemClock = systemClock;
+        mUserInteractor = userInteractor;
 
         mProximitySensor.setTag(PROXIMITY_SENSOR_TAG);
         mProximitySensor.setDelay(SensorManager.SENSOR_DELAY_GAME);
diff --git a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
index bf4fba8..7d73896 100644
--- a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
+++ b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
@@ -19,18 +19,19 @@
 import android.app.WallpaperColors;
 import android.app.WallpaperManager;
 import android.content.Context;
-import android.graphics.Color;
 import android.os.UserHandle;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.internal.colorextraction.types.ExtractionType;
 import com.android.internal.colorextraction.types.Tonal;
-import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+
+import dagger.Lazy;
 
 import java.io.PrintWriter;
 import java.util.Arrays;
@@ -45,22 +46,23 @@
         ConfigurationController.ConfigurationListener {
     private static final String TAG = "SysuiColorExtractor";
     private final Tonal mTonal;
-    private boolean mHasMediaArtwork;
     private final GradientColors mNeutralColorsLock;
-    private final GradientColors mBackdropColors;
+    private Lazy<SelectedUserInteractor> mUserInteractor;
 
     @Inject
     public SysuiColorExtractor(
             Context context,
             ConfigurationController configurationController,
-            DumpManager dumpManager) {
+            DumpManager dumpManager,
+            Lazy<SelectedUserInteractor> userInteractor) {
         this(
                 context,
                 new Tonal(context),
                 configurationController,
                 context.getSystemService(WallpaperManager.class),
                 dumpManager,
-                false /* immediately */);
+                false /* immediately */,
+                userInteractor);
     }
 
     @VisibleForTesting
@@ -70,15 +72,14 @@
             ConfigurationController configurationController,
             WallpaperManager wallpaperManager,
             DumpManager dumpManager,
-            boolean immediately) {
+            boolean immediately,
+            Lazy<SelectedUserInteractor> userInteractor) {
         super(context, type, immediately, wallpaperManager);
         mTonal = type instanceof Tonal ? (Tonal) type : new Tonal(context);
         mNeutralColorsLock = new GradientColors();
         configurationController.addCallback(this);
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
-
-        mBackdropColors = new GradientColors();
-        mBackdropColors.setMainColor(Color.BLACK);
+        mUserInteractor = userInteractor;
 
         // Listen to all users instead of only the current one.
         if (wallpaperManager.isWallpaperSupported()) {
@@ -100,7 +101,7 @@
 
     @Override
     public void onColorsChanged(WallpaperColors colors, int which, int userId) {
-        if (userId != KeyguardUpdateMonitor.getCurrentUser()) {
+        if (userId != mUserInteractor.get().getSelectedUserId()) {
             // Colors do not belong to current user, ignoring.
             return;
         }
@@ -116,14 +117,6 @@
         triggerColorsChanged(WallpaperManager.FLAG_SYSTEM | WallpaperManager.FLAG_LOCK);
     }
 
-    @Override
-    public GradientColors getColors(int which, int type) {
-        if (mHasMediaArtwork && (which & WallpaperManager.FLAG_LOCK) != 0) {
-            return mBackdropColors;
-        }
-        return super.getColors(which, type);
-    }
-
     /**
      * Colors that should be using for scrims.
      *
@@ -133,14 +126,7 @@
      * - Black otherwise
      */
     public GradientColors getNeutralColors() {
-        return mHasMediaArtwork ? mBackdropColors : mNeutralColorsLock;
-    }
-
-    public void setHasMediaArtwork(boolean hasBackdrop) {
-        if (mHasMediaArtwork != hasBackdrop) {
-            mHasMediaArtwork = hasBackdrop;
-            triggerColorsChanged(WallpaperManager.FLAG_LOCK);
-        }
+        return mNeutralColorsLock;
     }
 
     @Override
@@ -157,7 +143,5 @@
         pw.println("    system: " + Arrays.toString(system));
         pw.println("    lock: " + Arrays.toString(lock));
         pw.println("  Neutral colors: " + mNeutralColorsLock);
-        pw.println("  Has media backdrop: " + mHasMediaArtwork);
-
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt
index f9c4f29..1a214ba 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.communal.data.model
 
-import com.android.systemui.communal.shared.CommunalContentSize
+import com.android.systemui.communal.shared.model.CommunalContentSize
 
 /** Metadata for the default widgets */
 data class CommunalWidgetMetadata(
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index f13b62f..77025dc 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -20,6 +20,7 @@
 import android.appwidget.AppWidgetManager
 import android.appwidget.AppWidgetProviderInfo
 import android.content.BroadcastReceiver
+import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
@@ -28,11 +29,12 @@
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.communal.data.model.CommunalWidgetMetadata
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
-import com.android.systemui.communal.shared.CommunalContentSize
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
@@ -43,6 +45,7 @@
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 
 /** Encapsulates the state of widgets for communal mode. */
@@ -52,6 +55,9 @@
 
     /** Widgets that are allowed to render in the glanceable hub */
     val communalWidgetAllowlist: List<CommunalWidgetMetadata>
+
+    /** A flow of information about all the communal widgets to show. */
+    val communalWidgets: Flow<List<CommunalWidgetContentModel>>
 }
 
 @SysUISingleton
@@ -67,7 +73,7 @@
     private val userManager: UserManager,
     private val userTracker: UserTracker,
     @CommunalLog logBuffer: LogBuffer,
-    featureFlags: FeatureFlags,
+    featureFlags: FeatureFlagsClassic,
 ) : CommunalWidgetRepository {
     companion object {
         const val TAG = "CommunalWidgetRepository"
@@ -88,49 +94,59 @@
     // Widgets that should be rendered in communal mode.
     private val widgets: HashMap<Int, CommunalAppWidgetInfo> = hashMapOf()
 
-    private val isUserUnlocked: Flow<Boolean> = callbackFlow {
-        if (!featureFlags.isEnabled(Flags.WIDGET_ON_KEYGUARD)) {
-            awaitClose()
-        }
-
-        fun isUserUnlockingOrUnlocked(): Boolean {
-            return userManager.isUserUnlockingOrUnlocked(userTracker.userHandle)
-        }
-
-        fun send() {
-            trySendWithFailureLogging(isUserUnlockingOrUnlocked(), TAG)
-        }
-
-        if (isUserUnlockingOrUnlocked()) {
-            send()
-            awaitClose()
-        } else {
-            val receiver =
-                object : BroadcastReceiver() {
-                    override fun onReceive(context: Context?, intent: Intent?) {
-                        send()
-                    }
+    private val isUserUnlocked: Flow<Boolean> =
+        callbackFlow {
+                if (!communalRepository.isCommunalEnabled) {
+                    awaitClose()
                 }
 
-            broadcastDispatcher.registerReceiver(
-                receiver,
-                IntentFilter(Intent.ACTION_USER_UNLOCKED),
-            )
+                fun isUserUnlockingOrUnlocked(): Boolean {
+                    return userManager.isUserUnlockingOrUnlocked(userTracker.userHandle)
+                }
 
-            awaitClose { broadcastDispatcher.unregisterReceiver(receiver) }
+                fun send() {
+                    trySendWithFailureLogging(isUserUnlockingOrUnlocked(), TAG)
+                }
+
+                if (isUserUnlockingOrUnlocked()) {
+                    send()
+                    awaitClose()
+                } else {
+                    val receiver =
+                        object : BroadcastReceiver() {
+                            override fun onReceive(context: Context?, intent: Intent?) {
+                                send()
+                            }
+                        }
+
+                    broadcastDispatcher.registerReceiver(
+                        receiver,
+                        IntentFilter(Intent.ACTION_USER_UNLOCKED),
+                    )
+
+                    awaitClose { broadcastDispatcher.unregisterReceiver(receiver) }
+                }
+            }
+            .distinctUntilChanged()
+
+    private val isHostActive: Flow<Boolean> =
+        isUserUnlocked.map {
+            if (it) {
+                startListening()
+                true
+            } else {
+                stopListening()
+                clearWidgets()
+                false
+            }
         }
-    }
 
     override val stopwatchAppWidgetInfo: Flow<CommunalAppWidgetInfo?> =
-        isUserUnlocked.map { isUserUnlocked ->
-            if (!isUserUnlocked) {
-                clearWidgets()
-                stopListening()
+        isHostActive.map { isHostActive ->
+            if (!isHostActive || !featureFlags.isEnabled(Flags.WIDGET_ON_KEYGUARD)) {
                 return@map null
             }
 
-            startListening()
-
             val providerInfo =
                 appWidgetManager.installedProviders.find {
                     it.loadLabel(packageManager).equals(WIDGET_LABEL)
@@ -144,6 +160,42 @@
             return@map addWidget(providerInfo)
         }
 
+    override val communalWidgets: Flow<List<CommunalWidgetContentModel>> =
+        isHostActive.map { isHostActive ->
+            if (!isHostActive) {
+                return@map emptyList()
+            }
+
+            // The allowlist should be fetched from the local database with all the metadata tied to
+            // a widget, including an appWidgetId if it has been bound. Before the database is set
+            // up, we are going to use the app widget host as the source of truth for bound widgets,
+            // and rebind each time on boot.
+
+            // Remove all previously bound widgets.
+            appWidgetHost.appWidgetIds.forEach { appWidgetHost.deleteAppWidgetId(it) }
+
+            val inventory = mutableListOf<CommunalWidgetContentModel>()
+
+            // Bind all widgets from the allowlist.
+            communalWidgetAllowlist.forEach {
+                val id = appWidgetHost.allocateAppWidgetId()
+                appWidgetManager.bindAppWidgetId(
+                    id,
+                    ComponentName.unflattenFromString(it.componentName),
+                )
+
+                inventory.add(
+                    CommunalWidgetContentModel(
+                        appWidgetId = id,
+                        providerInfo = appWidgetManager.getAppWidgetInfo(id),
+                        priority = it.priority,
+                    )
+                )
+            }
+
+            return@map inventory.toList()
+        }
+
     private fun getWidgetAllowlist(): List<CommunalWidgetMetadata> {
         val componentNames =
             applicationContext.resources.getStringArray(R.array.config_communalWidgetAllowlist)
@@ -151,7 +203,7 @@
             CommunalWidgetMetadata(
                 componentName = name,
                 priority = componentNames.size - index,
-                sizes = listOf(CommunalContentSize.HALF)
+                sizes = listOf(CommunalContentSize.HALF),
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 04bb6ae..6238707 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -18,7 +18,8 @@
 
 import com.android.systemui.communal.data.repository.CommunalRepository
 import com.android.systemui.communal.data.repository.CommunalWidgetRepository
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import com.android.systemui.dagger.SysUISingleton
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
@@ -38,4 +39,12 @@
 
     /** A flow of info about the widget to be displayed, or null if widget is unavailable. */
     val appWidgetInfo: Flow<CommunalAppWidgetInfo?> = widgetRepository.stopwatchAppWidgetInfo
+
+    /**
+     * A flow of information about widgets to be shown in communal hub.
+     *
+     * Currently only showing persistent widgets that have been bound to the app widget service
+     * (have an allocated id).
+     */
+    val widgetContent: Flow<List<CommunalWidgetContentModel>> = widgetRepository.communalWidgets
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt
deleted file mode 100644
index 0bd7d86..0000000
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.android.systemui.communal.shared
-
-/** Supported sizes for communal content in the layout grid. */
-enum class CommunalContentSize {
-    FULL,
-    HALF,
-    THIRD,
-}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalAppWidgetInfo.kt
similarity index 94%
rename from packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
rename to packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalAppWidgetInfo.kt
index 0803a01..109ed2d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalAppWidgetInfo.kt
@@ -15,7 +15,7 @@
  *
  */
 
-package com.android.systemui.communal.shared
+package com.android.systemui.communal.shared.model
 
 import android.appwidget.AppWidgetProviderInfo
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentCategory.kt
similarity index 67%
copy from packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
copy to packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentCategory.kt
index 0803a01..7f05b9c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentCategory.kt
@@ -12,15 +12,14 @@
  * 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.communal.shared
+package com.android.systemui.communal.shared.model
 
-import android.appwidget.AppWidgetProviderInfo
+enum class CommunalContentCategory {
+    /** The content persists in the communal hub until removed by the user. */
+    PERSISTENT,
 
-/** A data class that stores info about an app widget that displays in communal mode. */
-data class CommunalAppWidgetInfo(
-    val providerInfo: AppWidgetProviderInfo,
-    val appWidgetId: Int,
-)
+    /** The content temporarily shows up in the communal hub when certain conditions are met. */
+    TRANSIENT,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
copy to packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
index 0803a01..39a6476 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
@@ -12,15 +12,18 @@
  * 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.communal.shared
+package com.android.systemui.communal.shared.model
 
-import android.appwidget.AppWidgetProviderInfo
+/** Supported sizes for communal content in the layout grid. */
+enum class CommunalContentSize {
+    /** Content takes the full height of the column. */
+    FULL,
 
-/** A data class that stores info about an app widget that displays in communal mode. */
-data class CommunalAppWidgetInfo(
-    val providerInfo: AppWidgetProviderInfo,
-    val appWidgetId: Int,
-)
+    /** Content takes half of the height of the column. */
+    HALF,
+
+    /** Content takes a third of the height of the column. */
+    THIRD,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
similarity index 80%
copy from packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
copy to packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
index 0803a01..e141dc4 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
@@ -12,15 +12,15 @@
  * 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.communal.shared
+package com.android.systemui.communal.shared.model
 
 import android.appwidget.AppWidgetProviderInfo
 
-/** A data class that stores info about an app widget that displays in communal mode. */
-data class CommunalAppWidgetInfo(
-    val providerInfo: AppWidgetProviderInfo,
+/** Encapsulates data for a communal widget. */
+data class CommunalWidgetContentModel(
     val appWidgetId: Int,
+    val providerInfo: AppWidgetProviderInfo,
+    val priority: Int,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt
index 2a08d7f..0daf7b5 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt
@@ -20,7 +20,7 @@
 import android.appwidget.AppWidgetManager
 import android.content.Context
 import android.util.SizeF
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
 import com.android.systemui.communal.ui.view.CommunalWidgetWrapper
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.log.LogBuffer
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/model/CommunalContentUiModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/model/CommunalContentUiModel.kt
new file mode 100644
index 0000000..98060dc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/model/CommunalContentUiModel.kt
@@ -0,0 +1,15 @@
+package com.android.systemui.communal.ui.model
+
+import android.view.View
+import com.android.systemui.communal.shared.model.CommunalContentSize
+
+/**
+ * Encapsulates data for a communal content that holds a view.
+ *
+ * This model stays in the UI layer.
+ */
+data class CommunalContentUiModel(
+    val view: View,
+    val size: CommunalContentSize,
+    val priority: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index ddeb1d6..25c64ea 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -16,17 +16,43 @@
 
 package com.android.systemui.communal.ui.viewmodel
 
+import android.appwidget.AppWidgetHost
+import android.content.Context
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
+import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.ui.model.CommunalContentUiModel
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
 
 @SysUISingleton
 class CommunalViewModel
 @Inject
 constructor(
+    @Application private val context: Context,
+    private val appWidgetHost: AppWidgetHost,
+    communalInteractor: CommunalInteractor,
     tutorialInteractor: CommunalTutorialInteractor,
 ) {
     /** Whether communal hub should show tutorial content. */
     val showTutorialContent: Flow<Boolean> = tutorialInteractor.isTutorialAvailable
+
+    /** List of widgets to be displayed in the communal hub. */
+    val widgetContent: Flow<List<CommunalContentUiModel>> =
+        communalInteractor.widgetContent.map {
+            it.map {
+                // TODO(b/306406256): As adding and removing widgets functionalities are
+                // supported, cache the host views so they're not recreated each time.
+                val hostView = appWidgetHost.createView(context, it.appWidgetId, it.providerInfo)
+                return@map CommunalContentUiModel(
+                    view = hostView,
+                    priority = it.priority,
+                    // All widgets have HALF size.
+                    size = CommunalContentSize.HALF,
+                )
+            }
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalWidgetViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalWidgetViewModel.kt
index 8fba342..d7bbea6 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalWidgetViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalWidgetViewModel.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.communal.ui.viewmodel
 
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
index 9221832b..4bdea75 100644
--- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
+++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
@@ -79,4 +79,7 @@
         context: Context,
         viewModel: CommunalViewModel,
     ): View
+
+    /** Creates a container that hosts the communal UI and handles gesture transitions. */
+    fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 5d6949b..d8ff535 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -57,7 +57,6 @@
 import com.android.systemui.statusbar.gesture.GesturePointerEventListener
 import com.android.systemui.statusbar.notification.InstantAppNotifier
 import com.android.systemui.statusbar.phone.KeyguardLiftController
-import com.android.systemui.statusbar.phone.LockscreenWallpaper
 import com.android.systemui.statusbar.phone.ScrimController
 import com.android.systemui.statusbar.phone.StatusBarHeadsUpChangeListener
 import com.android.systemui.stylus.StylusUsiPowerStartable
@@ -344,11 +343,6 @@
 
     @Binds
     @IntoMap
-    @ClassKey(LockscreenWallpaper::class)
-    abstract fun bindLockscreenWallpaper(impl: LockscreenWallpaper): CoreStartable
-
-    @Binds
-    @IntoMap
     @ClassKey(ScrimController::class)
     abstract fun bindScrimController(impl: ScrimController): CoreStartable
 
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
index d19efbd..3f21533 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
@@ -21,6 +21,7 @@
 import android.widget.TextView
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.phone.SystemUIBottomSheetDialog
+import com.android.systemui.statusbar.policy.ConfigurationController
 
 /**
  * Dialog used to decide what to do with a connected display.
@@ -32,8 +33,9 @@
     context: Context,
     private val onStartMirroringClickListener: View.OnClickListener,
     private val onCancelMirroring: View.OnClickListener,
+    configurationController: ConfigurationController? = null,
     theme: Int = R.style.Theme_SystemUI_Dialog,
-) : SystemUIBottomSheetDialog(context, theme) {
+) : SystemUIBottomSheetDialog(context, configurationController, theme) {
 
     private lateinit var mirrorButton: TextView
     private lateinit var dismissButton: TextView
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
index 86ef439..91f535d 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
 import com.android.systemui.display.ui.view.MirroringConfirmationDialog
+import com.android.systemui.statusbar.policy.ConfigurationController
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
@@ -41,7 +42,8 @@
     private val context: Context,
     private val connectedDisplayInteractor: ConnectedDisplayInteractor,
     @Application private val scope: CoroutineScope,
-    @Background private val bgDispatcher: CoroutineDispatcher
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    private val configurationController: ConfigurationController
 ) {
 
     private var dialog: Dialog? = null
@@ -71,7 +73,8 @@
                     onCancelMirroring = {
                         scope.launch(bgDispatcher) { pendingDisplay.ignore() }
                         hideDialog()
-                    }
+                    },
+                    configurationController
                 )
                 .apply { show() }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java b/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java
index 5eb9808..9c13a8c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java
@@ -18,6 +18,7 @@
 
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.doze.dagger.DozeScope;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 import javax.inject.Inject;
 
@@ -28,16 +29,19 @@
 public class DozeAuthRemover implements DozeMachine.Part {
 
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final SelectedUserInteractor mSelectedUserInteractor;
 
     @Inject
-    public DozeAuthRemover(KeyguardUpdateMonitor keyguardUpdateMonitor) {
+    public DozeAuthRemover(KeyguardUpdateMonitor keyguardUpdateMonitor,
+            SelectedUserInteractor selectedUserInteractor) {
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mSelectedUserInteractor = selectedUserInteractor;
     }
 
     @Override
     public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
         if (newState == DozeMachine.State.DOZE || newState == DozeMachine.State.DOZE_AOD) {
-            int currentUser = KeyguardUpdateMonitor.getCurrentUser();
+            int currentUser = mSelectedUserInteractor.getSelectedUserId();
             if (mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(currentUser)) {
                 mKeyguardUpdateMonitor.clearBiometricRecognized();
             }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
index 7da2cf1..ba57918 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
@@ -31,13 +31,13 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.UdfpsController;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.dagger.DozeScope;
 import com.android.systemui.doze.dagger.WrappedService;
 import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.wakelock.SettableWakeLock;
 import com.android.systemui.util.wakelock.WakeLock;
 
@@ -81,6 +81,7 @@
     @Nullable private UdfpsController mUdfpsController;
     private final DozeLog mDozeLog;
     private final DozeScreenBrightness mDozeScreenBrightness;
+    private final SelectedUserInteractor mSelectedUserInteractor;
 
     private int mPendingScreenState = Display.STATE_UNKNOWN;
     private SettableWakeLock mWakeLock;
@@ -95,7 +96,8 @@
             AuthController authController,
             Provider<UdfpsController> udfpsControllerProvider,
             DozeLog dozeLog,
-            DozeScreenBrightness dozeScreenBrightness) {
+            DozeScreenBrightness dozeScreenBrightness,
+            SelectedUserInteractor selectedUserInteractor) {
         mDozeService = service;
         mHandler = handler;
         mParameters = parameters;
@@ -105,6 +107,7 @@
         mUdfpsControllerProvider = udfpsControllerProvider;
         mDozeLog = dozeLog;
         mDozeScreenBrightness = dozeScreenBrightness;
+        mSelectedUserInteractor = selectedUserInteractor;
 
         updateUdfpsController();
         if (mUdfpsController == null) {
@@ -113,7 +116,7 @@
     }
 
     private void updateUdfpsController() {
-        if (mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())) {
+        if (mAuthController.isUdfpsEnrolled(mSelectedUserInteractor.getSelectedUserId())) {
             mUdfpsController = mUdfpsControllerProvider.get();
         } else {
             mUdfpsController = null;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 07efbfe..3194942 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -48,12 +48,11 @@
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.UiEventLoggerImpl;
-import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.plugins.SensorManagerPlugin;
-import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.policy.DevicePostureController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.sensors.AsyncSensorManager;
 import com.android.systemui.util.sensors.ProximitySensor;
 import com.android.systemui.util.settings.SecureSettings;
@@ -101,7 +100,7 @@
     private final SecureSettings mSecureSettings;
     private final DevicePostureController mDevicePostureController;
     private final AuthController mAuthController;
-    private final UserTracker mUserTracker;
+    private final SelectedUserInteractor mSelectedUserInteractor;
     private final boolean mScreenOffUdfpsEnabled;
 
     // Sensors
@@ -158,7 +157,7 @@
             SecureSettings secureSettings,
             AuthController authController,
             DevicePostureController devicePostureController,
-            UserTracker userTracker
+            SelectedUserInteractor selectedUserInteractor
     ) {
         mSensorManager = sensorManager;
         mConfig = config;
@@ -171,15 +170,15 @@
         mProximitySensor.setTag(TAG);
         mSelectivelyRegisterProxSensors = dozeParameters.getSelectivelyRegisterSensorsUsingProx();
         mListeningProxSensors = !mSelectivelyRegisterProxSensors;
+        mSelectedUserInteractor = selectedUserInteractor;
         mScreenOffUdfpsEnabled =
-                config.screenOffUdfpsEnabled(KeyguardUpdateMonitor.getCurrentUser());
+                config.screenOffUdfpsEnabled(mSelectedUserInteractor.getSelectedUserId());
         mDevicePostureController = devicePostureController;
         mDevicePosture = mDevicePostureController.getDevicePosture();
         mAuthController = authController;
-        mUserTracker = userTracker;
 
         mUdfpsEnrolled =
-                mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser());
+                mAuthController.isUdfpsEnrolled(mSelectedUserInteractor.getSelectedUserId());
         mAuthController.addCallback(mAuthControllerCallback);
         mTriggerSensors = new TriggerSensor[] {
                 new TriggerSensor(
@@ -255,7 +254,8 @@
                         new SensorManagerPlugin.Sensor(TYPE_WAKE_DISPLAY),
                         Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE,
                         mConfig.wakeScreenGestureAvailable()
-                          && mConfig.alwaysOnEnabled(mUserTracker.getUserId()),
+                          && mConfig.alwaysOnEnabled(
+                                  mSelectedUserInteractor.getSelectedUserId(true)),
                         DozeLog.REASON_SENSOR_WAKE_UP_PRESENCE,
                         false /* reports touch coordinates */,
                         false /* touchscreen */
@@ -296,12 +296,13 @@
 
     private boolean udfpsLongPressConfigured() {
         return mUdfpsEnrolled
-                && (mConfig.alwaysOnEnabled(mUserTracker.getUserId()) || mScreenOffUdfpsEnabled);
+                && (mConfig.alwaysOnEnabled(mSelectedUserInteractor.getSelectedUserId(true))
+                || mScreenOffUdfpsEnabled);
     }
 
     private boolean quickPickUpConfigured() {
         return mUdfpsEnrolled
-                && mConfig.quickPickupSensorEnabled(KeyguardUpdateMonitor.getCurrentUser());
+                && mConfig.quickPickupSensorEnabled(mSelectedUserInteractor.getSelectedUserId());
     }
 
     /**
@@ -471,7 +472,7 @@
     private final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
         @Override
         public void onChange(boolean selfChange, Collection<Uri> uris, int flags, int userId) {
-            if (userId != mUserTracker.getUserId()) {
+            if (userId != mSelectedUserInteractor.getSelectedUserId(true)) {
                 return;
             }
             for (TriggerSensor s : mTriggerSensors) {
@@ -697,13 +698,13 @@
         }
 
         protected boolean enabledBySetting() {
-            if (!mConfig.enabled(mUserTracker.getUserId())) {
+            if (!mConfig.enabled(mSelectedUserInteractor.getSelectedUserId(true))) {
                 return false;
             } else if (TextUtils.isEmpty(mSetting)) {
                 return true;
             }
             return mSecureSettings.getIntForUser(mSetting, mSettingDefault ? 1 : 0,
-                    mUserTracker.getUserId()) != 0;
+                    mSelectedUserInteractor.getSelectedUserId(true)) != 0;
         }
 
         @Override
@@ -873,7 +874,7 @@
 
         private void updateUdfpsEnrolled() {
             mUdfpsEnrolled = mAuthController.isUdfpsEnrolled(
-                    KeyguardUpdateMonitor.getCurrentUser());
+                    mSelectedUserInteractor.getSelectedUserId());
             for (TriggerSensor sensor : mTriggerSensors) {
                 if (REASON_SENSOR_QUICK_PICKUP == sensor.mPulseReason) {
                     sensor.setConfigured(quickPickUpConfigured());
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 85272a6..795c3d4 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -50,6 +50,7 @@
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.Assert;
 import com.android.systemui.util.sensors.AsyncSensorManager;
 import com.android.systemui.util.sensors.ProximityCheck;
@@ -102,6 +103,7 @@
     private final AuthController mAuthController;
     private final KeyguardStateController mKeyguardStateController;
     private final UserTracker mUserTracker;
+    private final SelectedUserInteractor mSelectedUserInteractor;
     private final UiEventLogger mUiEventLogger;
 
     private long mNotificationPulseTime;
@@ -201,7 +203,8 @@
             SessionTracker sessionTracker,
             KeyguardStateController keyguardStateController,
             DevicePostureController devicePostureController,
-            UserTracker userTracker) {
+            UserTracker userTracker,
+            SelectedUserInteractor selectedUserInteractor) {
         mContext = context;
         mDozeHost = dozeHost;
         mConfig = config;
@@ -213,7 +216,7 @@
 
         mDozeSensors = new DozeSensors(mContext.getResources(), mSensorManager, dozeParameters,
                 config, wakeLock, this::onSensor, this::onProximityFar, dozeLog, proximitySensor,
-                secureSettings, authController, devicePostureController, userTracker);
+                secureSettings, authController, devicePostureController, selectedUserInteractor);
         mDockManager = dockManager;
         mProxCheck = proxCheck;
         mDozeLog = dozeLog;
@@ -222,6 +225,7 @@
         mUiEventLogger = uiEventLogger;
         mKeyguardStateController = keyguardStateController;
         mUserTracker = userTracker;
+        mSelectedUserInteractor = selectedUserInteractor;
     }
 
     @Override
@@ -246,7 +250,7 @@
             return;
         }
         mNotificationPulseTime = SystemClock.elapsedRealtime();
-        if (!mConfig.pulseOnNotificationEnabled(mUserTracker.getUserId())) {
+        if (!mConfig.pulseOnNotificationEnabled(mSelectedUserInteractor.getSelectedUserId(true))) {
             runIfNotNull(onPulseSuppressedListener);
             mDozeLog.tracePulseDropped("pulseOnNotificationsDisabled");
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 0a43c84..10fac4d 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -180,6 +180,10 @@
     @JvmField
     val NEW_AOD_TRANSITION = unreleasedFlag("new_aod_transition", teamfood = true)
 
+    // TODO(b/305984787):
+    @JvmField
+    val REFACTOR_GETCURRENTUSER = unreleasedFlag("refactor_getcurrentuser", teamfood = true)
+
     /** Flag to control the migration of face auth to modern architecture. */
     // TODO(b/262838215): Tracking bug
     @JvmField val FACE_AUTH_REFACTOR = releasedFlag("face_auth_refactor")
@@ -259,7 +263,7 @@
     // TODO(b/290652751): Tracking bug.
     @JvmField
     val MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA =
-        releasedFlag("migrate_split_keyguard_bottom_area")
+        unreleasedFlag("migrate_split_keyguard_bottom_area", teamfood = true)
 
     // TODO(b/297037052): Tracking bug.
     @JvmField
@@ -274,7 +278,7 @@
 
     /** Migrate the lock icon view to the new keyguard root view. */
     // TODO(b/286552209): Tracking bug.
-    @JvmField val MIGRATE_LOCK_ICON = releasedFlag("migrate_lock_icon")
+    @JvmField val MIGRATE_LOCK_ICON = unreleasedFlag("migrate_lock_icon", teamfood = true)
 
     // TODO(b/288276738): Tracking bug.
     @JvmField val WIDGET_ON_KEYGUARD = unreleasedFlag("widget_on_keyguard")
@@ -419,7 +423,7 @@
         releasedFlag("incompatible_charging_battery_icon")
 
     // TODO(b/293585143): Tracking Bug
-    val INSTANT_TETHER = unreleasedFlag("instant_tether")
+    val INSTANT_TETHER = unreleasedFlag("instant_tether", teamfood = true)
 
     // TODO(b/294588085): Tracking Bug
     val WIFI_SECONDARY_NETWORKS = releasedFlag("wifi_secondary_networks")
@@ -545,12 +549,6 @@
     val LOCKSCREEN_ENABLE_LANDSCAPE =
             unreleasedFlag("lockscreen.enable_landscape")
 
-    // TODO(b/273443374): Tracking Bug
-    @Keep
-    @JvmField
-    val LOCKSCREEN_LIVE_WALLPAPER =
-        sysPropBooleanFlag("persist.wm.debug.lockscreen_live_wallpaper", default = true)
-
     // TODO(b/281648899): Tracking bug
     @Keep
     @JvmField
@@ -769,11 +767,10 @@
 
     // TODO(b/283740863): Tracking Bug
     @JvmField
-    val ENABLE_NEW_PRIVACY_DIALOG =
-        unreleasedFlag("enable_new_privacy_dialog", teamfood = true)
+    val ENABLE_NEW_PRIVACY_DIALOG = releasedFlag("enable_new_privacy_dialog")
 
     // TODO(b/289573946): Tracking Bug
-    @JvmField val PRECOMPUTED_TEXT = unreleasedFlag("precomputed_text", teamfood = true)
+    @JvmField val PRECOMPUTED_TEXT = releasedFlag("precomputed_text")
 
     // TODO(b/302087895): Tracking Bug
     @JvmField val CALL_LAYOUT_ASYNC_SET_DATA = unreleasedFlag("call_layout_async_set_data")
@@ -796,8 +793,7 @@
 
     /** TODO(b/296223317): Enables the new keyguard presentation containing a clock. */
     @JvmField
-    val ENABLE_CLOCK_KEYGUARD_PRESENTATION =
-        unreleasedFlag("enable_clock_keyguard_presentation", teamfood = true)
+    val ENABLE_CLOCK_KEYGUARD_PRESENTATION = releasedFlag("enable_clock_keyguard_presentation")
 
     /** Enable the Compose implementation of the PeopleSpaceActivity. */
     @JvmField
@@ -817,8 +813,7 @@
 
     // TODO(b/287205379): Tracking bug
     @JvmField
-    val QS_CONTAINER_GRAPH_OPTIMIZER = unreleasedFlag( "qs_container_graph_optimizer",
-            teamfood = true)
+    val QS_CONTAINER_GRAPH_OPTIMIZER = releasedFlag( "qs_container_graph_optimizer")
 
     /** Enable showing a dialog when clicking on Quick Settings bluetooth tile. */
     @JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index c6c1f79..5cc2e0a 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -134,6 +134,7 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
 import com.android.systemui.telephony.TelephonyListenerManager;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.EmergencyDialerConstants;
 import com.android.systemui.util.RingerModeTracker;
 import com.android.systemui.util.settings.GlobalSettings;
@@ -195,6 +196,7 @@
     private final IDreamManager mDreamManager;
     private final DevicePolicyManager mDevicePolicyManager;
     private final LockPatternUtils mLockPatternUtils;
+    private final SelectedUserInteractor mSelectedUserInteractor;
     private final TelephonyListenerManager mTelephonyListenerManager;
     private final KeyguardStateController mKeyguardStateController;
     private final BroadcastDispatcher mBroadcastDispatcher;
@@ -364,7 +366,8 @@
             PackageManager packageManager,
             ShadeController shadeController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
-            DialogLaunchAnimator dialogLaunchAnimator) {
+            DialogLaunchAnimator dialogLaunchAnimator,
+            SelectedUserInteractor selectedUserInteractor) {
         mContext = context;
         mWindowManagerFuncs = windowManagerFuncs;
         mAudioManager = audioManager;
@@ -399,6 +402,7 @@
         mShadeController = shadeController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mDialogLaunchAnimator = dialogLaunchAnimator;
+        mSelectedUserInteractor = selectedUserInteractor;
 
         // receive broadcasts
         IntentFilter filter = new IntentFilter();
@@ -713,7 +717,8 @@
                 mUiEventLogger,
                 mShadeController,
                 mKeyguardUpdateMonitor,
-                mLockPatternUtils);
+                mLockPatternUtils,
+                mSelectedUserInteractor);
 
         dialog.setOnDismissListener(this);
         dialog.setOnShowListener(this);
@@ -2222,6 +2227,7 @@
         private GestureDetector mGestureDetector;
         private final ShadeController mShadeController;
         private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+        private SelectedUserInteractor mSelectedUserInteractor;
         private LockPatternUtils mLockPatternUtils;
         private float mWindowDimAmount;
 
@@ -2300,7 +2306,8 @@
                 UiEventLogger uiEventLogger,
                 ShadeController shadeController,
                 KeyguardUpdateMonitor keyguardUpdateMonitor,
-                LockPatternUtils lockPatternUtils) {
+                LockPatternUtils lockPatternUtils,
+                SelectedUserInteractor selectedUserInteractor) {
             // We set dismissOnDeviceLock to false because we have a custom broadcast receiver to
             // dismiss this dialog when the device is locked.
             super(context, themeRes, false /* dismissOnDeviceLock */);
@@ -2321,6 +2328,7 @@
             mKeyguardUpdateMonitor = keyguardUpdateMonitor;
             mLockPatternUtils = lockPatternUtils;
             mGestureDetector = new GestureDetector(mContext, mGestureListener);
+            mSelectedUserInteractor = selectedUserInteractor;
         }
 
         @Override
@@ -2453,10 +2461,10 @@
             }
 
             // If user entered from the lock screen and smart lock was enabled, disable it
-            int user = KeyguardUpdateMonitor.getCurrentUser();
+            int user = mSelectedUserInteractor.getSelectedUserId();
             boolean userHasTrust = mKeyguardUpdateMonitor.getUserHasTrust(user);
             if (mKeyguardShowing && userHasTrust) {
-                mLockPatternUtils.requireCredentialEntry(KeyguardUpdateMonitor.getCurrentUser());
+                mLockPatternUtils.requireCredentialEntry(user);
                 showSmartLockDisabledMessage();
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index b45613e..2b1cdc2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -35,6 +35,8 @@
 import static android.view.WindowManager.TransitionOldType;
 import static android.view.WindowManager.TransitionType;
 
+import static com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER;
+
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
@@ -76,13 +78,13 @@
 import com.android.systemui.dagger.qualifiers.Application;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.power.shared.model.ScreenPowerState;
 import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier;
 import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindViewBinder;
 import com.android.systemui.keyguard.ui.binder.WindowManagerLockscreenVisibilityViewBinder;
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSurfaceBehindViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.WindowManagerLockscreenVisibilityViewModel;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
+import com.android.systemui.power.shared.model.ScreenPowerState;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.wm.shell.transition.ShellTransitions;
 import com.android.wm.shell.transition.Transitions;
@@ -200,7 +202,7 @@
     // Wrap Keyguard going away animation.
     // Note: Also used for wrapping occlude by Dream animation. It works (with some redundancy).
     public static IRemoteTransition wrap(final KeyguardViewMediator keyguardViewMediator,
-        final IRemoteAnimationRunner runner, final boolean lockscreenLiveWallpaperEnabled) {
+            final IRemoteAnimationRunner runner) {
         return new IRemoteTransition.Stub() {
 
             @GuardedBy("mLeashMap")
@@ -234,9 +236,8 @@
                     }
                 }
                 initAlphaForAnimationTargets(t, apps);
-                if (lockscreenLiveWallpaperEnabled) {
-                    initAlphaForAnimationTargets(t, wallpapers);
-                }
+                initAlphaForAnimationTargets(t, wallpapers);
+
                 t.apply();
 
                 runner.onAnimationStart(
@@ -599,11 +600,18 @@
             mKeyguardViewMediator.setSwitchingUser(switching);
         }
 
+        /**
+         * @deprecated This binder call is not listened to anymore. Instead the current user is
+         * tracked in SelectedUserInteractor.getSelectedUserId()
+         */
         @Override // Binder interface
+        @Deprecated
         public void setCurrentUser(int userId) {
-            trace("setCurrentUser userId=" + userId);
+            trace("Deprecated/NOT USED: setCurrentUser userId=" + userId);
             checkPermission();
-            mKeyguardViewMediator.setCurrentUser(userId);
+            if (!mFlags.isEnabled(REFACTOR_GETCURRENTUSER)) {
+                mKeyguardViewMediator.setCurrentUser(userId);
+            }
         }
 
         @Override // Binder interface
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 0bac40b..c8c06ae 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -707,8 +707,7 @@
                 return@postDelayed
             }
 
-            if ((wallpaperTargets?.isNotEmpty() == true) &&
-                    wallpaperManager.isLockscreenLiveWallpaperEnabled()) {
+            if ((wallpaperTargets?.isNotEmpty() == true)) {
                 fadeInWallpaper()
                 hideKeyguardViewAfterRemoteAnimation()
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 39742a0..4e6a872 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -38,6 +38,7 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
 import static com.android.systemui.DejankUtils.whitelistIpcs;
+import static com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER;
 import static com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.DREAMING_ANIMATION_DURATION_MS;
 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
@@ -163,6 +164,7 @@
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.settings.SecureSettings;
@@ -182,8 +184,6 @@
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
-
-
 import kotlinx.coroutines.CoroutineDispatcher;
 
 /**
@@ -617,6 +617,9 @@
         public void onUserSwitching(int userId) {
             if (DEBUG) Log.d(TAG, String.format("onUserSwitching %d", userId));
             synchronized (KeyguardViewMediator.this) {
+                if (mFeatureFlags.isEnabled(REFACTOR_GETCURRENTUSER)) {
+                    notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(userId));
+                }
                 resetKeyguardDonePendingLocked();
                 dismiss(null /* callback */, null /* message */);
                 adjustStatusBarLocked();
@@ -742,7 +745,7 @@
 
         @Override
         public void onBiometricAuthFailed(BiometricSourceType biometricSourceType) {
-            final int currentUser = KeyguardUpdateMonitor.getCurrentUser();
+            final int currentUser = mSelectedUserInteractor.getSelectedUserId();
             if (mLockPatternUtils.isSecure(currentUser)) {
                 mLockPatternUtils.getDevicePolicyManager().reportFailedBiometricAttempt(
                         currentUser);
@@ -760,7 +763,7 @@
 
         @Override
         public void onTrustChanged(int userId) {
-            if (userId == KeyguardUpdateMonitor.getCurrentUser()) {
+            if (userId == mSelectedUserInteractor.getSelectedUserId()) {
                 synchronized (KeyguardViewMediator.this) {
                     notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(userId));
                 }
@@ -769,7 +772,7 @@
 
         @Override
         public void onStrongAuthStateChanged(int userId) {
-            if (mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) {
+            if (mLockPatternUtils.isUserInLockdown(mSelectedUserInteractor.getSelectedUserId())) {
                 doKeyguardLocked(null);
             }
         }
@@ -784,7 +787,7 @@
 
         @Override
         public void keyguardDone(int targetUserId) {
-            if (targetUserId != KeyguardUpdateMonitor.getCurrentUser()) {
+            if (targetUserId != mSelectedUserInteractor.getSelectedUserId()) {
                 return;
             }
             if (DEBUG) Log.d(TAG, "keyguardDone");
@@ -807,7 +810,7 @@
         public void keyguardDonePending(int targetUserId) {
             Trace.beginSection("KeyguardViewMediator.mViewMediatorCallback#keyguardDonePending");
             if (DEBUG) Log.d(TAG, "keyguardDonePending");
-            if (targetUserId != KeyguardUpdateMonitor.getCurrentUser()) {
+            if (targetUserId != mSelectedUserInteractor.getSelectedUserId()) {
                 Trace.endSection();
                 return;
             }
@@ -888,7 +891,7 @@
 
         @Override
         public int getBouncerPromptReason() {
-            int currentUser = KeyguardUpdateMonitor.getCurrentUser();
+            int currentUser = mSelectedUserInteractor.getSelectedUserId();
             boolean trustAgentsEnabled = mUpdateMonitor.isTrustUsuallyManaged(currentUser);
             boolean biometricsEnrolled =
                     mUpdateMonitor.isUnlockingWithBiometricsPossible(currentUser);
@@ -1316,6 +1319,7 @@
 
     private DeviceConfigProxy mDeviceConfig;
     private DozeParameters mDozeParameters;
+    private SelectedUserInteractor mSelectedUserInteractor;
 
     private final KeyguardStateController mKeyguardStateController;
     private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
@@ -1396,7 +1400,8 @@
             @Main CoroutineDispatcher mainDispatcher,
             Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
             SystemPropertiesHelper systemPropertiesHelper,
-            Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager) {
+            Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager,
+            SelectedUserInteractor selectedUserInteractor) {
         mContext = context;
         mUserTracker = userTracker;
         mFalsingCollector = falsingCollector;
@@ -1436,6 +1441,7 @@
                     mInGestureNavigationMode = QuickStepContract.isGesturalMode(mode);
                 }));
         mDozeParameters = dozeParameters;
+        mSelectedUserInteractor = selectedUserInteractor;
 
         mStatusBarStateController = statusBarStateController;
         statusBarStateController.addCallback(this);
@@ -1493,25 +1499,27 @@
 
         mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
 
-        KeyguardUpdateMonitor.setCurrentUser(mUserTracker.getUserId());
+        if (!mFeatureFlags.isEnabled(REFACTOR_GETCURRENTUSER)) {
+            KeyguardUpdateMonitor.setCurrentUser(mUserTracker.getUserId());
+        }
 
         // Assume keyguard is showing (unless it's disabled) until we know for sure, unless Keyguard
         // is disabled.
         if (isKeyguardServiceEnabled()) {
             setShowingLocked(!shouldWaitForProvisioning()
                     && !mLockPatternUtils.isLockScreenDisabled(
-                            KeyguardUpdateMonitor.getCurrentUser()), true /* forceCallbacks */);
+                            mSelectedUserInteractor.getSelectedUserId()),
+                    true /* forceCallbacks */);
         } else {
             // The system's keyguard is disabled or missing.
             setShowingLocked(false /* showing */, true /* forceCallbacks */);
         }
 
-        boolean isLLwpEnabled = getWallpaperManager().isLockscreenLiveWallpaperEnabled();
         mKeyguardTransitions.register(
-                KeyguardService.wrap(this, getExitAnimationRunner(), isLLwpEnabled),
-                KeyguardService.wrap(this, getOccludeAnimationRunner(), isLLwpEnabled),
-                KeyguardService.wrap(this, getOccludeByDreamAnimationRunner(), isLLwpEnabled),
-                KeyguardService.wrap(this, getUnoccludeAnimationRunner(), isLLwpEnabled));
+                KeyguardService.wrap(this, getExitAnimationRunner()),
+                KeyguardService.wrap(this, getOccludeAnimationRunner()),
+                KeyguardService.wrap(this, getOccludeByDreamAnimationRunner()),
+                KeyguardService.wrap(this, getUnoccludeAnimationRunner()));
 
         final ContentResolver cr = mContext.getContentResolver();
 
@@ -1622,11 +1630,11 @@
             // Lock immediately based on setting if secure (user has a pin/pattern/password).
             // This also "locks" the device when not secure to provide easy access to the
             // camera while preventing unwanted input.
-            int currentUser = KeyguardUpdateMonitor.getCurrentUser();
+            int currentUser = mSelectedUserInteractor.getSelectedUserId();
             final boolean lockImmediately =
                     mLockPatternUtils.getPowerButtonInstantlyLocks(currentUser)
                             || !mLockPatternUtils.isSecure(currentUser);
-            long timeout = getLockTimeout(KeyguardUpdateMonitor.getCurrentUser());
+            long timeout = getLockTimeout(mSelectedUserInteractor.getSelectedUserId());
             mLockLater = false;
             if (mShowing && !mKeyguardStateController.isKeyguardGoingAway()) {
                 // If we are going to sleep but the keyguard is showing (and will continue to be
@@ -1807,7 +1815,7 @@
     }
 
     private void doKeyguardLaterLocked() {
-        long timeout = getLockTimeout(KeyguardUpdateMonitor.getCurrentUser());
+        long timeout = getLockTimeout(mSelectedUserInteractor.getSelectedUserId());
         if (timeout == 0) {
             doKeyguardLocked(null);
         } else {
@@ -1916,7 +1924,7 @@
 
     private void maybeSendUserPresentBroadcast() {
         if (mSystemReady && mLockPatternUtils.isLockScreenDisabled(
-                KeyguardUpdateMonitor.getCurrentUser())) {
+                mSelectedUserInteractor.getSelectedUserId())) {
             // Lock screen is disabled because the user has set the preference to "None".
             // In this case, send out ACTION_USER_PRESENT here instead of in
             // handleKeyguardDone()
@@ -1925,7 +1933,7 @@
             // Skipping the lockscreen because we're not yet provisioned, but we still need to
             // notify the StrongAuthTracker that it's now safe to run trust agents, in case the
             // user sets a credential later.
-            mLockPatternUtils.userPresent(KeyguardUpdateMonitor.getCurrentUser());
+            mLockPatternUtils.userPresent(mSelectedUserInteractor.getSelectedUserId());
         }
     }
 
@@ -1966,7 +1974,8 @@
             mExternallyEnabled = enabled;
 
             if (!enabled && mShowing) {
-                if (mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) {
+                if (mLockPatternUtils.isUserInLockdown(
+                        mSelectedUserInteractor.getSelectedUserId())) {
                     Log.d(TAG, "keyguardEnabled(false) overridden by user lockdown");
                     return;
                 }
@@ -2197,7 +2206,8 @@
     private void doKeyguardLocked(Bundle options) {
         // if another app is disabling us, don't show
         if (!mExternallyEnabled
-                && !mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) {
+                && !mLockPatternUtils.isUserInLockdown(
+                        mSelectedUserInteractor.getSelectedUserId())) {
             if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled");
 
             mNeedToReshowWhenReenabled = true;
@@ -2253,7 +2263,7 @@
         }
 
         boolean forceShow = options != null && options.getBoolean(OPTION_FORCE_SHOW, false);
-        if (mLockPatternUtils.isLockScreenDisabled(KeyguardUpdateMonitor.getCurrentUser())
+        if (mLockPatternUtils.isLockScreenDisabled(mSelectedUserInteractor.getSelectedUserId())
                 && !lockedOrMissing && !forceShow) {
             if (DEBUG) Log.d(TAG, "doKeyguard: not showing because lockscreen is off");
             return;
@@ -2384,7 +2394,7 @@
     }
 
     public boolean isSecure() {
-        return isSecure(KeyguardUpdateMonitor.getCurrentUser());
+        return isSecure(mSelectedUserInteractor.getSelectedUserId());
     }
 
     public boolean isSecure(int userId) {
@@ -2605,7 +2615,7 @@
      */
     private void handleKeyguardDone() {
         Trace.beginSection("KeyguardViewMediator#handleKeyguardDone");
-        final int currentUser = KeyguardUpdateMonitor.getCurrentUser();
+        final int currentUser = mSelectedUserInteractor.getSelectedUserId();
         mUiBgExecutor.execute(() -> {
             if (mLockPatternUtils.isSecure(currentUser)) {
                 mLockPatternUtils.getDevicePolicyManager().reportKeyguardDismissed(currentUser);
@@ -2631,7 +2641,7 @@
     private void sendUserPresentBroadcast() {
         synchronized (this) {
             if (mBootCompleted) {
-                int currentUserId = KeyguardUpdateMonitor.getCurrentUser();
+                int currentUserId = mSelectedUserInteractor.getSelectedUserId();
                 final UserHandle currentUser = new UserHandle(currentUserId);
                 final UserManager um = (UserManager) mContext.getSystemService(
                         Context.USER_SERVICE);
@@ -2679,7 +2689,7 @@
     private void playSound(int soundId) {
         if (soundId == 0) return;
         int lockscreenSoundsEnabled = mSystemSettings.getIntForUser(LOCKSCREEN_SOUNDS_ENABLED, 1,
-                KeyguardUpdateMonitor.getCurrentUser());
+                mSelectedUserInteractor.getSelectedUserId());
         if (lockscreenSoundsEnabled == 1) {
 
             mLockSounds.stop(mLockSoundStreamId);
@@ -2732,7 +2742,7 @@
      */
     private void handleShow(Bundle options) {
         Trace.beginSection("KeyguardViewMediator#handleShow");
-        final int currentUser = KeyguardUpdateMonitor.getCurrentUser();
+        final int currentUser = mSelectedUserInteractor.getSelectedUserId();
         if (mLockPatternUtils.isSecure(currentUser)) {
             mLockPatternUtils.getDevicePolicyManager().reportKeyguardSecured(currentUser);
         }
@@ -2787,7 +2797,7 @@
      * Schedule 4-hour idle timeout for non-strong biometrics when the device is locked
      */
     private void scheduleNonStrongBiometricIdleTimeout() {
-        final int currentUser = KeyguardUpdateMonitor.getCurrentUser();
+        final int currentUser = mSelectedUserInteractor.getSelectedUserId();
         // If unlocking with non-strong (i.e. weak or convenience) biometrics is possible, schedule
         // 4hr idle timeout after which non-strong biometrics can't be used to unlock device until
         // unlocking with strong biometric or primary auth (i.e. PIN/pattern/password)
@@ -3378,7 +3388,8 @@
             if (forceClearFlags) {
                 try {
                     mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
-                            mContext.getPackageName(), mUserTracker.getUserId());
+                            mContext.getPackageName(),
+                            mSelectedUserInteractor.getSelectedUserId(true));
                 } catch (RemoteException e) {
                     Log.d(TAG, "Failed to force clear flags", e);
                 }
@@ -3405,7 +3416,8 @@
 
             try {
                 mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
-                        mContext.getPackageName(), mUserTracker.getUserId());
+                        mContext.getPackageName(),
+                        mSelectedUserInteractor.getSelectedUserId(true));
             } catch (RemoteException e) {
                 Log.d(TAG, "Failed to set disable flags: " + flags, e);
             }
@@ -3728,7 +3740,8 @@
             for (int i = size - 1; i >= 0; i--) {
                 IKeyguardStateCallback callback = mKeyguardStateCallbacks.get(i);
                 try {
-                    callback.onShowingStateChanged(showing, KeyguardUpdateMonitor.getCurrentUser());
+                    callback.onShowingStateChanged(showing,
+                            mSelectedUserInteractor.getSelectedUserId());
                 } catch (RemoteException e) {
                     Slog.w(TAG, "Failed to call onShowingStateChanged", e);
                     if (e instanceof DeadObjectException) {
@@ -3771,10 +3784,11 @@
             mKeyguardStateCallbacks.add(callback);
             try {
                 callback.onSimSecureStateChanged(mUpdateMonitor.isSimPinSecure());
-                callback.onShowingStateChanged(mShowing, KeyguardUpdateMonitor.getCurrentUser());
+                callback.onShowingStateChanged(mShowing,
+                        mSelectedUserInteractor.getSelectedUserId());
                 callback.onInputRestrictedStateChanged(mInputRestricted);
                 callback.onTrustedChanged(mUpdateMonitor.getUserHasTrust(
-                        KeyguardUpdateMonitor.getCurrentUser()));
+                        mSelectedUserInteractor.getSelectedUserId()));
             } catch (RemoteException e) {
                 Slog.w(TAG, "Failed to call to IKeyguardStateCallback", e);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 019d428..8b93b17 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -68,6 +68,7 @@
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.settings.SecureSettings;
@@ -150,7 +151,8 @@
             @Main CoroutineDispatcher mainDispatcher,
             Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
             SystemPropertiesHelper systemPropertiesHelper,
-            Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager) {
+            Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager,
+            SelectedUserInteractor selectedUserInteractor) {
         return new KeyguardViewMediator(
                 context,
                 uiEventLogger,
@@ -194,7 +196,8 @@
                 mainDispatcher,
                 dreamingToLockscreenTransitionViewModel,
                 systemPropertiesHelper,
-                wmLockscreenVisibilityManager);
+                wmLockscreenVisibilityManager,
+                selectedUserInteractor);
     }
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index a4a3126..2dc4908 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -31,7 +31,6 @@
 import com.android.systemui.doze.DozeTransitionCallback
 import com.android.systemui.doze.DozeTransitionListener
 import com.android.systemui.dreams.DreamOverlayCallbackController
-import com.android.systemui.keyguard.ScreenLifecycle
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.keyguard.shared.model.DismissAction
@@ -41,9 +40,6 @@
 import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.phone.BiometricUnlockController
-import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnlockMode
-import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
@@ -111,6 +107,8 @@
     /** Is the always-on display available to be used? */
     val isAodAvailable: Flow<Boolean>
 
+    fun setAodAvailable(value: Boolean)
+
     /**
      * Observable for whether we are in doze state.
      *
@@ -160,6 +158,8 @@
     /** Observable for biometric unlock modes */
     val biometricUnlockState: Flow<BiometricUnlockModel>
 
+    fun setBiometricUnlockState(value: BiometricUnlockModel)
+
     /** Approximate location on the screen of the fingerprint sensor. */
     val fingerprintSensorLocation: Flow<Point?>
 
@@ -240,12 +240,9 @@
 @Inject
 constructor(
     statusBarStateController: StatusBarStateController,
-    screenLifecycle: ScreenLifecycle,
-    biometricUnlockController: BiometricUnlockController,
     private val keyguardStateController: KeyguardStateController,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val dozeTransitionListener: DozeTransitionListener,
-    private val dozeParameters: DozeParameters,
     private val authController: AuthController,
     private val dreamOverlayCallbackController: DreamOverlayCallbackController,
     @Main private val mainDispatcher: CoroutineDispatcher,
@@ -303,24 +300,12 @@
             }
             .distinctUntilChanged()
 
-    override val isAodAvailable: Flow<Boolean> =
-        conflatedCallbackFlow {
-                val callback =
-                    DozeParameters.Callback {
-                        trySendWithFailureLogging(
-                            dozeParameters.alwaysOn,
-                            TAG,
-                            "updated isAodAvailable"
-                        )
-                    }
+    private val _isAodAvailable = MutableStateFlow(false)
+    override val isAodAvailable: Flow<Boolean> = _isAodAvailable.asStateFlow()
 
-                dozeParameters.addCallback(callback)
-                // Adding the callback does not send an initial update.
-                trySendWithFailureLogging(dozeParameters.alwaysOn, TAG, "initial isAodAvailable")
-
-                awaitClose { dozeParameters.removeCallback(callback) }
-            }
-            .distinctUntilChanged()
+    override fun setAodAvailable(value: Boolean) {
+        _isAodAvailable.value = value
+    }
 
     override val isKeyguardOccluded: Flow<Boolean> =
         conflatedCallbackFlow {
@@ -506,30 +491,11 @@
                 statusBarStateIntToObject(statusBarStateController.state)
             )
 
-    override val biometricUnlockState: Flow<BiometricUnlockModel> = conflatedCallbackFlow {
-        fun dispatchUpdate() {
-            trySendWithFailureLogging(
-                biometricModeIntToObject(biometricUnlockController.mode),
-                TAG,
-                "biometric mode"
-            )
-        }
+    private val _biometricUnlockState = MutableStateFlow(BiometricUnlockModel.NONE)
+    override val biometricUnlockState = _biometricUnlockState.asStateFlow()
 
-        val callback =
-            object : BiometricUnlockController.BiometricUnlockEventsListener {
-                override fun onModeChanged(@WakeAndUnlockMode mode: Int) {
-                    dispatchUpdate()
-                }
-
-                override fun onResetMode() {
-                    dispatchUpdate()
-                }
-            }
-
-        biometricUnlockController.addListener(callback)
-        dispatchUpdate()
-
-        awaitClose { biometricUnlockController.removeListener(callback) }
+    override fun setBiometricUnlockState(value: BiometricUnlockModel) {
+        _biometricUnlockState.value = value
     }
 
     override val fingerprintSensorLocation: Flow<Point?> = conflatedCallbackFlow {
@@ -662,20 +628,6 @@
         }
     }
 
-    private fun biometricModeIntToObject(@WakeAndUnlockMode value: Int): BiometricUnlockModel {
-        return when (value) {
-            0 -> BiometricUnlockModel.NONE
-            1 -> BiometricUnlockModel.WAKE_AND_UNLOCK
-            2 -> BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
-            3 -> BiometricUnlockModel.SHOW_BOUNCER
-            4 -> BiometricUnlockModel.ONLY_WAKE
-            5 -> BiometricUnlockModel.UNLOCK_COLLAPSING
-            6 -> BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
-            7 -> BiometricUnlockModel.DISMISS_BOUNCER
-            else -> throw IllegalArgumentException("Invalid BiometricUnlockModel value: $value")
-        }
-    }
-
     private fun dozeMachineStateToModel(state: DozeMachine.State): DozeStateModel {
         return when (state) {
             DozeMachine.State.UNINITIALIZED -> DozeStateModel.UNINITIALIZED
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index 84cd3ef..3eef6aa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import java.util.UUID
@@ -48,8 +49,8 @@
  * [TransitionInteractor]. These interactors will call [startTransition] and [updateTransition] on
  * this repository.
  *
- * To print all transitions to logcat to help with debugging, run this command:
- * adb shell settings put global systemui/buffer/KeyguardLog VERBOSE
+ * To print all transitions to logcat to help with debugging, run this command: adb shell settings
+ * put global systemui/buffer/KeyguardLog VERBOSE
  *
  * This will print all keyguard transitions to logcat with the KeyguardTransitionAuditLogger tag.
  */
@@ -73,11 +74,8 @@
     /**
      * Begin a transition from one state to another. Transitions are interruptible, and will issue a
      * [TransitionStep] with state = [TransitionState.CANCELED] before beginning the next one.
-     *
-     * When canceled, there are two options: to continue from the current position of the prior
-     * transition, or to reset the position. When [resetIfCanceled] == true, it will do the latter.
      */
-    fun startTransition(info: TransitionInfo, resetIfCanceled: Boolean = false): UUID?
+    fun startTransition(info: TransitionInfo): UUID?
 
     /**
      * Allows manual control of a transition. When calling [startTransition], the consumer must pass
@@ -138,10 +136,7 @@
         )
     }
 
-    override fun startTransition(
-        info: TransitionInfo,
-        resetIfCanceled: Boolean,
-    ): UUID? {
+    override fun startTransition(info: TransitionInfo): UUID? {
         if (lastStep.from == info.from && lastStep.to == info.to) {
             Log.i(TAG, "Duplicate call to start the transition, rejecting: $info")
             return null
@@ -149,10 +144,10 @@
         val startingValue =
             if (lastStep.transitionState != TransitionState.FINISHED) {
                 Log.i(TAG, "Transition still active: $lastStep, canceling")
-                if (resetIfCanceled) {
-                    0f
-                } else {
-                    lastStep.value
+                when (info.modeOnCanceled) {
+                    TransitionModeOnCanceled.LAST_VALUE -> lastStep.value
+                    TransitionModeOnCanceled.RESET -> 0f
+                    TransitionModeOnCanceled.REVERSE -> 1f - lastStep.value
                 }
             } else {
                 0f
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricUnlockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricUnlockInteractor.kt
new file mode 100644
index 0000000..cb003a7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricUnlockInteractor.kt
@@ -0,0 +1,42 @@
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_DISMISS_BOUNCER
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_NONE
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_ONLY_WAKE
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_SHOW_BOUNCER
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_UNLOCK_COLLAPSING
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
+import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnlockMode
+import javax.inject.Inject
+
+@SysUISingleton
+class BiometricUnlockInteractor
+@Inject
+constructor(
+    private val keyguardRepository: KeyguardRepository,
+) {
+
+    fun setBiometricUnlockState(@WakeAndUnlockMode unlockStateInt: Int) {
+        val state = biometricModeIntToObject(unlockStateInt)
+        keyguardRepository.setBiometricUnlockState(state)
+    }
+
+    private fun biometricModeIntToObject(@WakeAndUnlockMode value: Int): BiometricUnlockModel {
+        return when (value) {
+            MODE_NONE -> BiometricUnlockModel.NONE
+            MODE_WAKE_AND_UNLOCK -> BiometricUnlockModel.WAKE_AND_UNLOCK
+            MODE_WAKE_AND_UNLOCK_PULSING -> BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
+            MODE_SHOW_BOUNCER -> BiometricUnlockModel.SHOW_BOUNCER
+            MODE_ONLY_WAKE -> BiometricUnlockModel.ONLY_WAKE
+            MODE_UNLOCK_COLLAPSING -> BiometricUnlockModel.UNLOCK_COLLAPSING
+            MODE_WAKE_AND_UNLOCK_FROM_DREAM -> BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
+            MODE_DISMISS_BOUNCER -> BiometricUnlockModel.DISMISS_BOUNCER
+            else -> throw IllegalArgumentException("Invalid BiometricUnlockModel value: $value")
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt
index 0c898be..af1802f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt
@@ -28,6 +28,10 @@
     private val keyguardRepository: KeyguardRepository,
 ) {
 
+    fun setAodAvailable(value: Boolean) {
+        keyguardRepository.setAodAvailable(value)
+    }
+
     fun setIsDozing(isDozing: Boolean) {
         keyguardRepository.setIsDozing(isDozing)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index d06f31f..7e360cf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -26,13 +26,13 @@
 import com.android.systemui.util.kotlin.Utils.Companion.toQuint
 import com.android.systemui.util.kotlin.sample
 import com.android.wm.shell.animation.Interpolators
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
-import javax.inject.Inject
-import kotlin.time.Duration.Companion.milliseconds
 
 @SysUISingleton
 class FromAlternateBouncerTransitionInteractor
@@ -130,11 +130,16 @@
     override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
             interpolator = Interpolators.LINEAR
-            duration = TRANSITION_DURATION_MS.inWholeMilliseconds
+            duration =
+                when (toState) {
+                    KeyguardState.GONE -> TO_GONE_DURATION
+                    else -> TRANSITION_DURATION_MS
+                }.inWholeMilliseconds
         }
     }
 
     companion object {
         val TRANSITION_DURATION_MS = 300.milliseconds
+        val TO_GONE_DURATION = 500.milliseconds
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 6e0aa4c..a331a66 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
@@ -65,8 +66,21 @@
                 )
                 .collect { (_, lastStartedStep, occluded) ->
                     if (lastStartedStep.to == KeyguardState.AOD) {
-                        startTransitionTo(
+                        val toState =
                             if (occluded) KeyguardState.OCCLUDED else KeyguardState.LOCKSCREEN
+                        val modeOnCanceled =
+                            if (
+                                toState == KeyguardState.LOCKSCREEN &&
+                                    lastStartedStep.from == KeyguardState.LOCKSCREEN
+                            ) {
+                                TransitionModeOnCanceled.REVERSE
+                            } else {
+                                TransitionModeOnCanceled.LAST_VALUE
+                            }
+
+                        startTransitionTo(
+                            toState = toState,
+                            modeOnCanceled = modeOnCanceled,
                         )
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index c67153a..eace0c7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
@@ -114,8 +115,9 @@
                 .collect { (isAsleep, lastStartedStep, isAodAvailable) ->
                     if (lastStartedStep.to == KeyguardState.GONE && isAsleep) {
                         startTransitionTo(
-                            if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING,
-                            resetIfCancelled = true,
+                            toState =
+                                if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING,
+                            modeOnCanceled = TransitionModeOnCanceled.RESET,
                         )
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index c39a4c9..d44a9d8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
 import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
 import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.shade.data.repository.ShadeRepository
@@ -340,8 +341,20 @@
                 )
                 .collect { (isAsleep, lastStartedStep, isAodAvailable) ->
                     if (lastStartedStep.to == KeyguardState.LOCKSCREEN && isAsleep) {
-                        startTransitionTo(
+                        val toState =
                             if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
+                        val modeOnCanceled =
+                            if (
+                                toState == KeyguardState.AOD &&
+                                    lastStartedStep.from == KeyguardState.AOD
+                            ) {
+                                TransitionModeOnCanceled.REVERSE
+                            } else {
+                                TransitionModeOnCanceled.LAST_VALUE
+                            }
+                        startTransitionTo(
+                            toState = toState,
+                            modeOnCanceled = modeOnCanceled,
                         )
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index ad2ec69..24b6661 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -18,7 +18,6 @@
 
 import android.animation.ValueAnimator
 import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.flags.FeatureFlags
@@ -26,20 +25,22 @@
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.kotlin.Utils.Companion.toQuad
 import com.android.systemui.util.kotlin.Utils.Companion.toQuint
 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
 import com.android.wm.shell.animation.Interpolators
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
-import javax.inject.Inject
-import kotlin.time.Duration.Companion.milliseconds
 
 @SysUISingleton
 class FromPrimaryBouncerTransitionInteractor
@@ -51,6 +52,7 @@
     private val keyguardInteractor: KeyguardInteractor,
     private val flags: FeatureFlags,
     private val keyguardSecurityModel: KeyguardSecurityModel,
+    private val selectedUserInteractor: SelectedUserInteractor,
     private val powerInteractor: PowerInteractor,
 ) :
     TransitionInteractor(
@@ -132,7 +134,7 @@
                 .collect {
                     (
                         isBouncerShowing,
-                            isAwake,
+                        isAwake,
                         lastStartedTransitionStep,
                         occluded,
                         isActiveDreamLockscreenHosted) ->
@@ -162,8 +164,7 @@
                     ),
                     ::toQuad
                 )
-                .collect {
-                    (isBouncerShowing, isAsleep, lastStartedTransitionStep, isAodAvailable)
+                .collect { (isBouncerShowing, isAsleep, lastStartedTransitionStep, isAodAvailable)
                     ->
                     if (
                         !isBouncerShowing &&
@@ -181,25 +182,24 @@
     private fun listenForPrimaryBouncerToDreamingLockscreenHosted() {
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
-                    .sample(
-                            combine(
-                                    keyguardInteractor.isActiveDreamLockscreenHosted,
-                                    transitionInteractor.startedKeyguardTransitionStep,
-                                    ::Pair
-                            ),
-                            ::toTriple
-                    )
-                    .collect { (isBouncerShowing,
-                                       isActiveDreamLockscreenHosted,
-                                       lastStartedTransitionStep) ->
-                        if (
-                                !isBouncerShowing &&
-                                isActiveDreamLockscreenHosted &&
-                                lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER
-                        ) {
-                            startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
-                        }
+                .sample(
+                    combine(
+                        keyguardInteractor.isActiveDreamLockscreenHosted,
+                        transitionInteractor.startedKeyguardTransitionStep,
+                        ::Pair
+                    ),
+                    ::toTriple
+                )
+                .collect {
+                    (isBouncerShowing, isActiveDreamLockscreenHosted, lastStartedTransitionStep) ->
+                    if (
+                        !isBouncerShowing &&
+                            isActiveDreamLockscreenHosted &&
+                            lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER
+                    ) {
+                        startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
                     }
+                }
         }
     }
 
@@ -221,7 +221,7 @@
                     ) {
                         val securityMode =
                             keyguardSecurityModel.getSecurityMode(
-                                KeyguardUpdateMonitor.getCurrentUser()
+                                selectedUserInteractor.getSelectedUserId()
                             )
                         // IME for password requires a slightly faster animation
                         val duration =
@@ -237,7 +237,7 @@
                                 getDefaultAnimatorForTransitionsToState(KeyguardState.GONE).apply {
                                     this.duration = duration.inWholeMilliseconds
                                 },
-                            resetIfCancelled = true
+                            modeOnCanceled = TransitionModeOnCanceled.RESET,
                         )
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
index cab6928..628e912 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
@@ -24,7 +24,7 @@
 import com.android.systemui.keyguard.shared.model.DismissAction
 import com.android.systemui.keyguard.shared.model.KeyguardDone
 import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.kotlin.Utils.Companion.toQuad
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
@@ -40,11 +40,11 @@
 @Inject
 constructor(
     trustRepository: TrustRepository,
-    val keyguardRepository: KeyguardRepository,
-    val primaryBouncerInteractor: PrimaryBouncerInteractor,
-    val alternateBouncerInteractor: AlternateBouncerInteractor,
-    val powerInteractor: PowerInteractor,
-    val userInteractor: UserInteractor,
+    private val keyguardRepository: KeyguardRepository,
+    primaryBouncerInteractor: PrimaryBouncerInteractor,
+    alternateBouncerInteractor: AlternateBouncerInteractor,
+    powerInteractor: PowerInteractor,
+    private val selectedUserInteractor: SelectedUserInteractor,
 ) {
     /*
      * Updates when a biometric has authenticated the device and is requesting to dismiss
@@ -82,7 +82,7 @@
      */
     private val primaryAuthenticated: Flow<Unit> =
         primaryBouncerInteractor.keyguardAuthenticatedPrimaryAuth
-            .filter { authedUserId -> authedUserId == userInteractor.getSelectedUserId() }
+            .filter { authedUserId -> authedUserId == selectedUserInteractor.getSelectedUserId() }
             .map {} // map to Unit
 
     /*
@@ -92,7 +92,7 @@
      */
     private val userRequestedBouncerWhenAlreadyAuthenticated: Flow<Unit> =
         primaryBouncerInteractor.userRequestedBouncerWhenAlreadyAuthenticated
-            .filter { authedUserId -> authedUserId == userInteractor.getSelectedUserId() }
+            .filter { authedUserId -> authedUserId == selectedUserInteractor.getSelectedUserId() }
             .map {} // map to Unit
 
     /** Updates when keyguardDone should be requested. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index fbe26de..b0b8577 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -143,6 +143,11 @@
     val dozingToLockscreenTransition: Flow<TransitionStep> =
         repository.transition(DOZING, LOCKSCREEN)
 
+    /** Receive all [TransitionStep] matching a filter of [from]->[to] */
+    fun transition(from: KeyguardState, to: KeyguardState): Flow<TransitionStep> {
+        return repository.transition(from, to)
+    }
+
     /**
      * AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <->
      * Lockscreen (0f).
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index 54c6d5f..7601808 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.util.kotlin.sample
 import java.util.UUID
 import kotlinx.coroutines.CoroutineScope
@@ -49,7 +50,7 @@
     fun startTransitionTo(
         toState: KeyguardState,
         animator: ValueAnimator? = getDefaultAnimatorForTransitionsToState(toState),
-        resetIfCancelled: Boolean = false,
+        modeOnCanceled: TransitionModeOnCanceled = TransitionModeOnCanceled.LAST_VALUE
     ): UUID? {
         if (
             fromState != transitionInteractor.startedKeyguardState.value &&
@@ -73,8 +74,8 @@
                 fromState,
                 toState,
                 animator,
-            ),
-            resetIfCancelled
+                modeOnCanceled,
+            )
         )
     }
 
@@ -91,8 +92,8 @@
                     // so use the last finishedKeyguardState to determine the overriding FROM state
                     if (finishedKeyguardState == fromState) {
                         startTransitionTo(
-                            KeyguardState.OCCLUDED,
-                            resetIfCancelled = true,
+                            toState = KeyguardState.OCCLUDED,
+                            modeOnCanceled = TransitionModeOnCanceled.RESET,
                         )
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt
index bfccf3fe..7a37365 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt
@@ -22,7 +22,13 @@
     val ownerName: String,
     val from: KeyguardState,
     val to: KeyguardState,
-    val animator: ValueAnimator?, // 'null' animator signal manual control
+    /** [null] animator signals manual control, otherwise transition run by the animator */
+    val animator: ValueAnimator?,
+    /**
+     * If the transition resets in the cancellation of another transition, use this mode to
+     * determine how to continue.
+     */
+    val modeOnCanceled: TransitionModeOnCanceled = TransitionModeOnCanceled.LAST_VALUE,
 ) {
     override fun toString(): String =
         "TransitionInfo(ownerName=$ownerName, from=$from, to=$to, " +
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionModeOnCanceled.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionModeOnCanceled.kt
new file mode 100644
index 0000000..56f90bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionModeOnCanceled.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.shared.model
+
+/** When canceled, provide different ways to start the next transition. */
+enum class TransitionModeOnCanceled {
+    /** Proceed from the last value. If canceled at .7, start from .7 and end at 1 */
+    LAST_VALUE,
+    /** Start over from 0. If canceled at .7, start from 0 and end at 1 */
+    RESET,
+    /** Reverse the transition. If canceled at .7, start from 1-.7 (0.3) and end at 1 */
+    REVERSE
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt
index f14552b..87d8164 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt
@@ -25,20 +25,18 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardDismissInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardDone
 import com.android.systemui.log.core.LogLevel
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.launch
 
 /** Handles keyguard dismissal requests. */
-@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class KeyguardDismissBinder
 @Inject
 constructor(
     private val interactor: KeyguardDismissInteractor,
-    private val userInteractor: UserInteractor,
+    private val selectedUserInteractor: SelectedUserInteractor,
     private val viewMediatorCallback: ViewMediatorCallback,
     @Application private val scope: CoroutineScope,
     private val keyguardLogger: KeyguardLogger,
@@ -55,11 +53,15 @@
                 when (keyguardDoneTiming) {
                     KeyguardDone.LATER -> {
                         log("keyguardDonePending")
-                        viewMediatorCallback.keyguardDonePending(userInteractor.getSelectedUserId())
+                        viewMediatorCallback.keyguardDonePending(
+                            selectedUserInteractor.getSelectedUserId()
+                        )
                     }
                     else -> {
                         log("keyguardDone")
-                        viewMediatorCallback.keyguardDone(userInteractor.getSelectedUserId())
+                        viewMediatorCallback.keyguardDone(
+                            selectedUserInteractor.getSelectedUserId()
+                        )
                     }
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 7ccabfe..4d5c503 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -233,7 +233,7 @@
         view.setOnHierarchyChangeListener(
             object : OnHierarchyChangeListener {
                 override fun onChildViewAdded(parent: View, child: View) {
-                    childViews.put(child.id, view)
+                    childViews.put(child.id, child)
                 }
 
                 override fun onChildViewRemoved(parent: View, child: View) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt
index 342a440..9371d4e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt
@@ -50,8 +50,11 @@
 
     override fun addViews(constraintLayout: ConstraintLayout) {
         if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
-            LayoutInflater.from(constraintLayout.context)
-                .inflate(R.layout.ambient_indication, constraintLayout, true)
+            val view =
+                LayoutInflater.from(constraintLayout.context)
+                    .inflate(R.layout.ambient_indication, constraintLayout, false)
+
+            constraintLayout.addView(view)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
new file mode 100644
index 0000000..023d16ca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
@@ -0,0 +1,42 @@
+/*
+ * 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 com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor.Companion.TO_GONE_DURATION
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
+import com.android.systemui.keyguard.shared.model.ScrimAlpha
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Breaks down ALTERNATE_BOUNCER->GONE transition into discrete steps for corresponding views to
+ * consume.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class AlternateBouncerToGoneTransitionViewModel
+@Inject
+constructor(
+    bouncerToGoneFlows: BouncerToGoneFlows,
+) {
+
+    /** Scrim alpha values */
+    val scrimAlpha: Flow<ScrimAlpha> =
+        bouncerToGoneFlows.scrimAlpha(TO_GONE_DURATION, ALTERNATE_BOUNCER)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
new file mode 100644
index 0000000..da74f2f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
@@ -0,0 +1,114 @@
+/*
+ * 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 com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.ScrimAlpha
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import dagger.Lazy
+import javax.inject.Inject
+import kotlin.time.Duration
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+
+/** ALTERNATE and PRIMARY bouncers common animations */
+@OptIn(ExperimentalCoroutinesApi::class)
+class BouncerToGoneFlows
+@Inject
+constructor(
+    private val interactor: KeyguardTransitionInteractor,
+    private val statusBarStateController: SysuiStatusBarStateController,
+    private val primaryBouncerInteractor: PrimaryBouncerInteractor,
+    private val keyguardDismissActionInteractor: Lazy<KeyguardDismissActionInteractor>,
+    private val featureFlags: FeatureFlagsClassic,
+    private val shadeInteractor: ShadeInteractor,
+) {
+    /** Common fade for scrim alpha values during *BOUNCER->GONE */
+    fun scrimAlpha(duration: Duration, fromState: KeyguardState): Flow<ScrimAlpha> {
+        return if (featureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+            keyguardDismissActionInteractor
+                .get()
+                .willAnimateDismissActionOnLockscreen
+                .flatMapLatest { createScrimAlphaFlow(duration, fromState) { it } }
+        } else {
+            createScrimAlphaFlow(
+                duration,
+                fromState,
+                primaryBouncerInteractor::willRunDismissFromKeyguard
+            )
+        }
+    }
+
+    private fun createScrimAlphaFlow(
+        duration: Duration,
+        fromState: KeyguardState,
+        willRunAnimationOnKeyguard: () -> Boolean
+    ): Flow<ScrimAlpha> {
+        var isShadeExpanded = false
+        var leaveShadeOpen: Boolean = false
+        var willRunDismissFromKeyguard: Boolean = false
+        val transitionAnimation =
+            KeyguardTransitionAnimationFlow(
+                transitionDuration = duration,
+                transitionFlow = interactor.transition(fromState, GONE)
+            )
+
+        return shadeInteractor.shadeExpansion.flatMapLatest { shadeExpansion ->
+            transitionAnimation
+                .createFlow(
+                    duration = duration,
+                    interpolator = EMPHASIZED_ACCELERATE,
+                    onStart = {
+                        leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide()
+                        willRunDismissFromKeyguard = willRunAnimationOnKeyguard()
+                        isShadeExpanded = shadeExpansion > 0f
+                    },
+                    onStep = { 1f - it },
+                )
+                .map {
+                    if (willRunDismissFromKeyguard) {
+                        if (isShadeExpanded) {
+                            ScrimAlpha(
+                                behindAlpha = it,
+                                notificationsAlpha = it,
+                            )
+                        } else {
+                            ScrimAlpha()
+                        }
+                    } else if (leaveShadeOpen) {
+                        ScrimAlpha(
+                            behindAlpha = 1f,
+                            notificationsAlpha = 1f,
+                        )
+                    } else {
+                        ScrimAlpha(behindAlpha = it)
+                    }
+                }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index 0783181..0e95be2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlagsClassic
@@ -24,6 +23,8 @@
 import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.keyguard.shared.model.ScrimAlpha
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -33,7 +34,6 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.map
 
 /**
  * Breaks down PRIMARY_BOUNCER->GONE transition into discrete steps for corresponding views to
@@ -49,11 +49,12 @@
     private val primaryBouncerInteractor: PrimaryBouncerInteractor,
     keyguardDismissActionInteractor: Lazy<KeyguardDismissActionInteractor>,
     featureFlags: FeatureFlagsClassic,
+    bouncerToGoneFlows: BouncerToGoneFlows,
 ) {
     private val transitionAnimation =
         KeyguardTransitionAnimationFlow(
             transitionDuration = TO_GONE_DURATION,
-            transitionFlow = interactor.primaryBouncerToGoneTransition,
+            transitionFlow = interactor.transition(PRIMARY_BOUNCER, GONE)
         )
 
     private var leaveShadeOpen: Boolean = false
@@ -110,38 +111,6 @@
         )
     }
 
-    /** Scrim alpha values */
     val scrimAlpha: Flow<ScrimAlpha> =
-        if (featureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
-            keyguardDismissActionInteractor
-                .get()
-                .willAnimateDismissActionOnLockscreen
-                .flatMapLatest { createScrimAlphaFlow { it } }
-        } else {
-            createScrimAlphaFlow(primaryBouncerInteractor::willRunDismissFromKeyguard)
-        }
-    private fun createScrimAlphaFlow(willRunAnimationOnKeyguard: () -> Boolean): Flow<ScrimAlpha> {
-        return transitionAnimation
-            .createFlow(
-                duration = TO_GONE_DURATION,
-                interpolator = EMPHASIZED_ACCELERATE,
-                onStart = {
-                    leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide()
-                    willRunDismissFromKeyguard = willRunAnimationOnKeyguard()
-                },
-                onStep = { 1f - it },
-            )
-            .map {
-                if (willRunDismissFromKeyguard) {
-                    ScrimAlpha()
-                } else if (leaveShadeOpen) {
-                    ScrimAlpha(
-                        behindAlpha = 1f,
-                        notificationsAlpha = 1f,
-                    )
-                } else {
-                    ScrimAlpha(behindAlpha = it)
-                }
-            }
-    }
+        bouncerToGoneFlows.scrimAlpha(TO_GONE_DURATION, PRIMARY_BOUNCER)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 67531ad..fd6b3f1 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -190,17 +190,6 @@
         }
     }
 
-    /**
-     * Provides a logging buffer for all logs related to Quick Settings tiles. This LogBuffer is
-     * unique for each tile.
-     * go/qs-tile-refactor
-     */
-    @Provides
-    @QSTilesDefaultLog
-    public static LogBuffer provideQuickSettingsTilesLogBuffer(LogBufferFactory factory) {
-        return factory.create("QSTileLog", 25 /* maxSize */, false /* systrace */);
-    }
-
     @Provides
     @QSTilesLogBuffers
     public static Map<TileSpec, LogBuffer> provideQuickSettingsTilesLogBufferCache() {
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesDefaultLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesDefaultLog.kt
deleted file mode 100644
index 6575cdd..0000000
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesDefaultLog.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.log.dagger
-
-import javax.inject.Qualifier
-
-/**
- * A default [com.android.systemui.log.LogBuffer] for QS tiles messages. It's used exclusively in
- * [com.android.systemui.qs.tiles.base.logging.QSTileLogger]. If you need to increase it for you
- * tile, add one to the map provided by the [QSTilesLogBuffers]
- */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class QSTilesDefaultLog
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index a48e56a..7cb5b3b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -731,6 +731,7 @@
                     removePlayer(existingSmartspaceMediaKey, dismissMediaData = false)
                 removedPlayer?.run {
                     debugLogger.logPotentialMemoryLeak(existingSmartspaceMediaKey)
+                    onDestroy()
                 }
             }
 
@@ -1302,6 +1303,7 @@
         val removedPlayer = removeMediaPlayer(key)
         if (removedPlayer != null && removedPlayer != player) {
             debugLogger?.logPotentialMemoryLeak(key)
+            removedPlayer.onDestroy()
         }
         val sortKey =
             MediaSortKey(
@@ -1329,6 +1331,7 @@
         val removedPlayer = removeMediaPlayer(key)
         if (!update && removedPlayer != null && removedPlayer != player) {
             debugLogger?.logPotentialMemoryLeak(key)
+            removedPlayer.onDestroy()
         }
         val sortKey =
             MediaSortKey(
@@ -1357,7 +1360,10 @@
             // MediaPlayer should not be visible
             // no need to set isDismissed flag.
             val removedPlayer = removeMediaPlayer(newKey)
-            removedPlayer?.run { debugLogger?.logPotentialMemoryLeak(newKey) }
+            removedPlayer?.run {
+                debugLogger?.logPotentialMemoryLeak(newKey)
+                onDestroy()
+            }
             mediaData.put(newKey, it)
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
index a53f0f1..d8cab6a 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
@@ -23,7 +23,7 @@
 import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_CAST as METRICS_CREATION_SOURCE_CAST
 import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER as METRICS_CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER
 import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN as METRICS_CREATION_SOURCE_UNKNOWN
-import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED as METRICS_STATE_PERMISSION_REQUEST_DISPLAYED
 import com.android.systemui.dagger.SysUISingleton
 import javax.inject.Inject
 
@@ -35,16 +35,26 @@
 class MediaProjectionMetricsLogger
 @Inject
 constructor(private val service: IMediaProjectionManager) {
+
     /**
      * Request to log that the permission was requested.
      *
+     * @param hostUid The UID of the package that initiates MediaProjection.
      * @param sessionCreationSource The entry point requesting permission to capture.
      */
-    fun notifyProjectionInitiated(sessionCreationSource: SessionCreationSource) {
-        notifyToServer(
-            MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED,
-            sessionCreationSource
-        )
+    fun notifyProjectionInitiated(hostUid: Int, sessionCreationSource: SessionCreationSource) {
+        try {
+            service.notifyPermissionRequestInitiated(
+                hostUid,
+                sessionCreationSource.toMetricsConstant()
+            )
+        } catch (e: RemoteException) {
+            Log.e(TAG, "Error notifying server of projection initiated", e)
+        }
+    }
+
+    fun notifyPermissionRequestDisplayed() {
+        notifyToServer(METRICS_STATE_PERMISSION_REQUEST_DISPLAYED, SessionCreationSource.UNKNOWN)
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index fa418fc..922f7e0 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -140,7 +140,7 @@
             if (MediaProjectionServiceHelper.hasProjectionPermission(mUid, mPackageName)) {
                 if (savedInstanceState == null) {
                     mMediaProjectionMetricsLogger.notifyProjectionInitiated(
-                            SessionCreationSource.APP);
+                            mUid, SessionCreationSource.APP);
                 }
                 final IMediaProjection projection =
                         MediaProjectionServiceHelper.createOrReuseProjection(mUid, mPackageName,
@@ -242,6 +242,7 @@
 
         if (savedInstanceState == null) {
             mMediaProjectionMetricsLogger.notifyProjectionInitiated(
+                    mUid,
                     appName == null
                             ? SessionCreationSource.CAST
                             : SessionCreationSource.APP);
@@ -249,6 +250,10 @@
 
         setUpDialog(mDialog);
         mDialog.show();
+
+        if (savedInstanceState == null) {
+            mMediaProjectionMetricsLogger.notifyPermissionRequestDisplayed();
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
index c91ed13..8e30740 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
@@ -42,7 +42,7 @@
 import com.android.systemui.security.data.repository.SecurityRepository
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.user.data.repository.UserSwitcherRepository
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.Flow
@@ -102,7 +102,7 @@
     private val deviceProvisionedController: DeviceProvisionedController,
     private val qsSecurityFooterUtils: QSSecurityFooterUtils,
     private val fgsManagerController: FgsManagerController,
-    private val userInteractor: UserInteractor,
+    private val userSwitcherInteractor: UserSwitcherInteractor,
     securityRepository: SecurityRepository,
     foregroundServicesRepository: ForegroundServicesRepository,
     userSwitcherRepository: UserSwitcherRepository,
@@ -178,6 +178,6 @@
     }
 
     override fun showUserSwitcher(expandable: Expandable) {
-        userInteractor.showUserSwitcher(expandable)
+        userSwitcherInteractor.showUserSwitcher(expandable)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index 959afd8..e27a59c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -30,12 +30,12 @@
 
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
 import com.android.systemui.animation.DialogCuj;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.qs.QSTile;
@@ -45,6 +45,7 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
 import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -69,6 +70,7 @@
     private final DialogLaunchAnimator mDialogLaunchAnimator;
     private final FeatureFlags mFlags;
     private final PanelInteractor mPanelInteractor;
+    private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
 
     private long mMillisUntilFinished = 0;
 
@@ -88,7 +90,8 @@
             KeyguardDismissUtil keyguardDismissUtil,
             KeyguardStateController keyguardStateController,
             DialogLaunchAnimator dialogLaunchAnimator,
-            PanelInteractor panelInteractor
+            PanelInteractor panelInteractor,
+            MediaProjectionMetricsLogger mediaProjectionMetricsLogger
     ) {
         super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
@@ -99,6 +102,7 @@
         mKeyguardStateController = keyguardStateController;
         mDialogLaunchAnimator = dialogLaunchAnimator;
         mPanelInteractor = panelInteractor;
+        mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;
     }
 
     @Override
@@ -190,6 +194,9 @@
             } else {
                 dialog.show();
             }
+
+            mMediaProjectionMetricsLogger.notifyPermissionRequestDisplayed();
+
             return false;
         };
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
index 1b0d5f9..2f8fe42 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
@@ -32,7 +32,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
@@ -43,6 +42,7 @@
 import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -55,7 +55,7 @@
     private final KeyguardStateController mKeyguard;
     protected IndividualSensorPrivacyController mSensorPrivacyController;
 
-    private final SafetyCenterManager mSafetyCenterManager;
+    private final Boolean mIsSafetyCenterEnabled;
 
     /**
      * @return Id of the sensor that will be toggled
@@ -89,7 +89,7 @@
                 statusBarStateController, activityStarter, qsLogger);
         mSensorPrivacyController = sensorPrivacyController;
         mKeyguard = keyguardStateController;
-        mSafetyCenterManager = safetyCenterManager;
+        mIsSafetyCenterEnabled = safetyCenterManager.isSafetyCenterEnabled();
         mSensorPrivacyController.observe(getLifecycle(), this);
     }
 
@@ -138,7 +138,7 @@
 
     @Override
     public Intent getLongClickIntent() {
-        if (mSafetyCenterManager.isSafetyCenterEnabled()) {
+        if (mIsSafetyCenterEnabled) {
             return new Intent(Settings.ACTION_PRIVACY_CONTROLS);
         } else {
             return new Intent(Settings.ACTION_PRIVACY_SETTINGS);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
index 70a683b..f8de365 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
@@ -19,8 +19,8 @@
 import androidx.annotation.GuardedBy
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
 import com.android.systemui.log.core.LogLevel
-import com.android.systemui.log.dagger.QSTilesDefaultLog
 import com.android.systemui.log.dagger.QSTilesLogBuffers
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.pipeline.shared.TileSpec
@@ -29,14 +29,13 @@
 import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
 import com.android.systemui.statusbar.StatusBarState
 import javax.inject.Inject
-import javax.inject.Provider
 
 @SysUISingleton
 class QSTileLogger
 @Inject
 constructor(
     @QSTilesLogBuffers logBuffers: Map<TileSpec, LogBuffer>,
-    @QSTilesDefaultLog private val defaultLogBufferProvider: Provider<LogBuffer>,
+    private val factory: LogBufferFactory,
     private val mStatusBarStateController: StatusBarStateController,
 ) {
     @GuardedBy("logBufferCache") private val logBufferCache = logBuffers.toMutableMap()
@@ -154,7 +153,13 @@
 
     private fun TileSpec.getLogBuffer(): LogBuffer =
         synchronized(logBufferCache) {
-            logBufferCache.getOrPut(this) { defaultLogBufferProvider.get() }
+            logBufferCache.getOrPut(this) {
+                factory.create(
+                    "QSTileLog_${this.getLogTag()}",
+                    BUFFER_MAX_SIZE /* maxSize */,
+                    false /* systrace */
+                )
+            }
         }
 
     private fun StateUpdateTrigger.toLogString(): String =
@@ -185,5 +190,6 @@
     private companion object {
         const val TAG_FORMAT_PREFIX = "QSLog"
         const val DATA_MAX_LENGTH = 50
+        const val BUFFER_MAX_SIZE = 25
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index f469c6b..f602da4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -155,6 +155,7 @@
         }
 
         mMediaProjectionMetricsLogger.notifyProjectionInitiated(
+                mUserContextProvider.getUserContext().getUserId(),
                 SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
 
         return flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
index cf1fbe3..d6f1ed9 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
@@ -68,6 +68,10 @@
      */
     @WeaklyReferencedCallback
     interface Callback {
+        /**
+         * Notifies that the current user will be changed.
+         */
+        fun onBeforeUserSwitching(newUser: Int) {}
 
         /**
          * Same as {@link onUserChanging(Int, Context, Runnable)} but the callback will be
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index 393a698..9f416bb 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -90,6 +90,7 @@
     private val isBackgroundUserSwitchEnabled: Boolean get() =
         featureFlagsProvider.get().isEnabled(Flags.USER_TRACKER_BACKGROUND_CALLBACKS)
 
+    @Deprecated("Use UserInteractor.getSelectedUserId()")
     override var userId: Int by SynchronizedDelegate(context.userId)
         protected set
 
@@ -226,6 +227,13 @@
     protected open fun handleBeforeUserSwitching(newUserId: Int) {
         Assert.isNotMainThread()
         setUserIdInternal(newUserId)
+
+        val list = synchronized(callbacks) {
+            callbacks.toList()
+        }
+        list.forEach {
+            it.callback.get()?.onBeforeUserSwitching(newUserId)
+        }
     }
 
     @WorkerThread
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index 1ecb127..ead10d6 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.settings.brightness;
 
+import static android.content.Intent.EXTRA_BRIGHTNESS_DIALOG_IS_FULL_WIDTH;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 import static android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KEY;
@@ -83,7 +84,7 @@
     private void setWindowAttributes() {
         final Window window = getWindow();
 
-        window.setGravity(Gravity.TOP | Gravity.LEFT);
+        window.setGravity(Gravity.TOP | Gravity.START);
         window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
         window.requestFeature(Window.FEATURE_NO_TITLE);
 
@@ -130,13 +131,14 @@
 
         Configuration configuration = getResources().getConfiguration();
         int orientation = configuration.orientation;
+        int screenWidth = getWindowManager().getDefaultDisplay().getWidth();
 
         if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
-            lp.width = getWindowManager().getDefaultDisplay().getWidth() / 2
-                    - lp.leftMargin * 2;
+            boolean shouldBeFullWidth = getIntent()
+                    .getBooleanExtra(EXTRA_BRIGHTNESS_DIALOG_IS_FULL_WIDTH, false);
+            lp.width = (shouldBeFullWidth ? screenWidth : screenWidth / 2) - horizontalMargin * 2;
         } else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
-            lp.width = getWindowManager().getDefaultDisplay().getWidth()
-                    - lp.leftMargin * 2;
+            lp.width = screenWidth - horizontalMargin * 2;
         }
 
         frame.setLayoutParams(lp);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 6f5e41f..0426388 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -47,7 +47,6 @@
 import android.view.WindowManagerGlobal;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dumpable;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -72,6 +71,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 import dagger.Lazy;
 
@@ -112,6 +112,7 @@
     private final KeyguardBypassController mKeyguardBypassController;
     private final Executor mBackgroundExecutor;
     private final AuthController mAuthController;
+    private final Lazy<SelectedUserInteractor> mUserInteractor;
     private final Lazy<ShadeInteractor> mShadeInteractorLazy;
     private ViewGroup mWindowRootView;
     private LayoutParams mLp;
@@ -157,7 +158,8 @@
             AuthController authController,
             ShadeExpansionStateManager shadeExpansionStateManager,
             Lazy<ShadeInteractor> shadeInteractorLazy,
-            ShadeWindowLogger logger) {
+            ShadeWindowLogger logger,
+            Lazy<SelectedUserInteractor> userInteractor) {
         mContext = context;
         mWindowRootViewComponentFactory = windowRootViewComponentFactory;
         mWindowManager = windowManager;
@@ -174,6 +176,7 @@
         mScreenOffAnimationController = screenOffAnimationController;
         dumpManager.registerDumpable(this);
         mAuthController = authController;
+        mUserInteractor = userInteractor;
         mLastKeyguardRotationAllowed = mKeyguardStateController.isKeyguardScreenRotationAllowed();
         mLockScreenDisplayTimeout = context.getResources()
                 .getInteger(R.integer.config_lockScreenDisplayTimeout);
@@ -348,7 +351,7 @@
             boolean onKeyguard = state.statusBarState == StatusBarState.KEYGUARD
                     && !state.keyguardFadingAway && !state.keyguardGoingAway;
             if (onKeyguard
-                    && mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())) {
+                    && mAuthController.isUdfpsEnrolled(mUserInteractor.get().getSelectedUserId())) {
                 // both max and min display refresh rate must be set to take effect:
                 mLpChanged.preferredMaxDisplayRefreshRate = mKeyguardPreferredRefreshRate;
                 mLpChanged.preferredMinDisplayRefreshRate = mKeyguardPreferredRefreshRate;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index d05dfe2..d869239 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -35,18 +35,20 @@
 import com.android.keyguard.LockIconViewController;
 import com.android.keyguard.dagger.KeyguardBouncerComponent;
 import com.android.systemui.Dumpable;
+import com.android.systemui.FeatureFlags;
 import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.back.domain.interactor.BackActionInteractor;
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.bouncer.ui.binder.KeyguardBouncerViewBinder;
 import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel;
 import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel;
+import com.android.systemui.compose.ComposeFacade;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FeatureFlagsClassic;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
@@ -55,7 +57,6 @@
 import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
 import com.android.systemui.log.BouncerLogger;
-import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.res.R;
 import com.android.systemui.shared.animation.DisableSubpixelTextTransitionListener;
 import com.android.systemui.statusbar.DragDownHelper;
@@ -75,6 +76,7 @@
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.window.StatusBarWindowStateController;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.time.SystemClock;
 
 import java.io.PrintWriter;
@@ -104,8 +106,10 @@
     private final PulsingGestureListener mPulsingGestureListener;
     private final LockscreenHostedDreamGestureListener mLockscreenHostedDreamGestureListener;
     private final NotificationInsetsController mNotificationInsetsController;
+    private final CommunalViewModel mCommunalViewModel;
     private final boolean mIsTrackpadCommonEnabled;
     private final FeatureFlags mFeatureFlags;
+    private final FeatureFlagsClassic mFeatureFlagsClassic;
     private final SysUIKeyEventHandler mSysUIKeyEventHandler;
     private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
     private final AlternateBouncerInteractor mAlternateBouncerInteractor;
@@ -128,8 +132,6 @@
     private final CentralSurfaces mService;
     private final DozeServiceHost mDozeServiceHost;
     private final DozeScrimController mDozeScrimController;
-    private final BackActionInteractor mBackActionInteractor;
-    private final PowerInteractor mPowerInteractor;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private DragDownHelper mDragDownHelper;
     private boolean mExpandingBelowNotch;
@@ -164,8 +166,6 @@
             CentralSurfaces centralSurfaces,
             DozeServiceHost dozeServiceHost,
             DozeScrimController dozeScrimController,
-            BackActionInteractor backActionInteractor,
-            PowerInteractor powerInteractor,
             NotificationShadeWindowController controller,
             Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
@@ -180,14 +180,17 @@
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
             PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
+            CommunalViewModel communalViewModel,
             NotificationExpansionRepository notificationExpansionRepository,
+            FeatureFlagsClassic featureFlagsClassic,
             FeatureFlags featureFlags,
             SystemClock clock,
             BouncerMessageInteractor bouncerMessageInteractor,
             BouncerLogger bouncerLogger,
             SysUIKeyEventHandler sysUIKeyEventHandler,
             PrimaryBouncerInteractor primaryBouncerInteractor,
-            AlternateBouncerInteractor alternateBouncerInteractor) {
+            AlternateBouncerInteractor alternateBouncerInteractor,
+            SelectedUserInteractor selectedUserInteractor) {
         mLockscreenShadeTransitionController = transitionController;
         mFalsingCollector = falsingCollector;
         mStatusBarStateController = statusBarStateController;
@@ -200,20 +203,20 @@
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mStatusBarWindowStateController = statusBarWindowStateController;
         mLockIconViewController = lockIconViewController;
-        mBackActionInteractor = backActionInteractor;
         mShadeLogger = shadeLogger;
         mService = centralSurfaces;
         mDozeServiceHost = dozeServiceHost;
         mDozeScrimController = dozeScrimController;
-        mPowerInteractor = powerInteractor;
         mNotificationShadeWindowController = controller;
         mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
         mAmbientState = ambientState;
         mPulsingGestureListener = pulsingGestureListener;
         mLockscreenHostedDreamGestureListener = lockscreenHostedDreamGestureListener;
         mNotificationInsetsController = notificationInsetsController;
-        mIsTrackpadCommonEnabled = featureFlags.isEnabled(TRACKPAD_GESTURE_COMMON);
+        mCommunalViewModel = communalViewModel;
+        mIsTrackpadCommonEnabled = featureFlagsClassic.isEnabled(TRACKPAD_GESTURE_COMMON);
         mFeatureFlags = featureFlags;
+        mFeatureFlagsClassic = featureFlagsClassic;
         mSysUIKeyEventHandler = sysUIKeyEventHandler;
         mPrimaryBouncerInteractor = primaryBouncerInteractor;
         mAlternateBouncerInteractor = alternateBouncerInteractor;
@@ -229,7 +232,8 @@
                 messageAreaControllerFactory,
                 bouncerMessageInteractor,
                 bouncerLogger,
-                featureFlags);
+                featureFlagsClassic,
+                selectedUserInteractor);
 
         collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(),
                 mLockscreenToDreamingTransition);
@@ -239,7 +243,7 @@
                 this::setExpandAnimationRunning);
 
         mClock = clock;
-        if (featureFlags.isEnabled(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION)) {
+        if (featureFlagsClassic.isEnabled(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION)) {
             unfoldTransitionProgressProvider.ifPresent(
                     progressProvider -> progressProvider.addCallback(
                             mDisableSubpixelTextTransitionListener));
@@ -268,7 +272,7 @@
         mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller);
         mPulsingWakeupGestureHandler = new GestureDetector(mView.getContext(),
                 mPulsingGestureListener);
-        if (mFeatureFlags.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) {
+        if (mFeatureFlagsClassic.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) {
             mDreamingWakeupGestureHandler = new GestureDetector(mView.getContext(),
                     mLockscreenHostedDreamGestureListener);
         }
@@ -440,7 +444,7 @@
                 }
 
                 boolean bouncerShowing;
-                if (mFeatureFlags.isEnabled(Flags.ALTERNATE_BOUNCER_VIEW)) {
+                if (mFeatureFlagsClassic.isEnabled(Flags.ALTERNATE_BOUNCER_VIEW)) {
                     bouncerShowing = mPrimaryBouncerInteractor.isBouncerShowing()
                             || mAlternateBouncerInteractor.isVisibleState();
                 } else {
@@ -452,7 +456,7 @@
                     if (mDragDownHelper.isDragDownEnabled()) {
                         // This handles drag down over lockscreen
                         boolean result = mDragDownHelper.onInterceptTouchEvent(ev);
-                        if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+                        if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
                             if (result) {
                                 mLastInterceptWasDragDownHelper = true;
                                 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
@@ -484,7 +488,7 @@
                 MotionEvent cancellation = MotionEvent.obtain(ev);
                 cancellation.setAction(MotionEvent.ACTION_CANCEL);
                 mStackScrollLayout.onInterceptTouchEvent(cancellation);
-                if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+                if (!mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
                     mNotificationPanelViewController.handleExternalInterceptTouch(cancellation);
                 }
                 cancellation.recycle();
@@ -499,7 +503,7 @@
                 if (mStatusBarKeyguardViewManager.onTouch(ev)) {
                     return true;
                 }
-                if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+                if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
                     if (mLastInterceptWasDragDownHelper && (mDragDownHelper.isDraggingDown())) {
                         // we still want to finish our drag down gesture when locking the screen
                         handled |= mDragDownHelper.onTouchEvent(ev) || handled;
@@ -564,8 +568,27 @@
         mDepthController.onPanelExpansionChanged(currentState);
     }
 
+    /**
+     * Sets up the communal hub UI if the {@link com.android.systemui.Flags#FLAG_COMMUNAL_HUB} flag
+     * is enabled.
+     *
+     * The layout lives in {@link R.id.communal_ui_container}.
+     */
+    public void setupCommunalHubLayout() {
+        if (!mFeatureFlags.communalHub() || !ComposeFacade.INSTANCE.isComposeAvailable()) {
+            return;
+        }
+
+        // Replace the placeholder view with the communal UI.
+        View communalPlaceholder = mView.findViewById(R.id.communal_ui_stub);
+        int index = mView.indexOfChild(communalPlaceholder);
+        mView.removeView(communalPlaceholder);
+        mView.addView(ComposeFacade.INSTANCE.createCommunalContainer(mView.getContext(),
+                mCommunalViewModel), index);
+    }
+
     private boolean didNotificationPanelInterceptEvent(MotionEvent ev) {
-        if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+        if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
             // Since NotificationStackScrollLayout is now a sibling of notification_panel, we need
             // to also ask NotificationPanelViewController directly, in order to process swipe up
             // events originating from notifications
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 3bbb2cf..3c68438 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -736,7 +736,11 @@
 
     /** Returns whether touches from the notification panel should be disallowed */
     public boolean disallowTouches() {
-        return mQs.disallowPanelTouches();
+        if (mQs != null) {
+            return mQs.disallowPanelTouches();
+        } else {
+            return false;
+        }
     }
 
     void setListening(boolean listening) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index e487a6f..2f68476 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -34,8 +34,7 @@
 import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
 import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository
-import com.android.systemui.user.domain.interactor.UserInteractor
-import com.android.systemui.util.kotlin.pairwise
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
 import javax.inject.Inject
 import javax.inject.Provider
 import kotlinx.coroutines.CoroutineScope
@@ -71,7 +70,7 @@
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
     powerInteractor: PowerInteractor,
     userSetupRepository: UserSetupRepository,
-    userInteractor: UserInteractor,
+    userSwitcherInteractor: UserSwitcherInteractor,
     sharedNotificationContainerInteractor: SharedNotificationContainerInteractor,
     repository: ShadeRepository,
 ) {
@@ -140,13 +139,6 @@
     /** Whether either the shade or QS is fully expanded. */
     val isAnyFullyExpanded: Flow<Boolean> = anyExpansion.map { it >= 1f }.distinctUntilChanged()
 
-    /** Whether either the shade or QS is expanding from a fully collapsed state. */
-    val isAnyExpanding: Flow<Boolean> =
-        anyExpansion
-            .pairwise(1f)
-            .map { (prev, curr) -> curr > 0f && curr < 1f && prev < 1f }
-            .distinctUntilChanged()
-
     /**
      * Whether either the shade or QS is partially or fully expanded, i.e. not fully collapsed. At
      * this time, this is not simply a matter of checking if either value in shadeExpansion and
@@ -227,7 +219,7 @@
             isDeviceProvisioned &&
                 // Disallow QS during setup if it's a simple user switcher. (The user intends to
                 // use the lock screen user switcher, QS is not needed.)
-                (isUserSetup || !userInteractor.isSimpleUserSwitcher) &&
+                (isUserSetup || !userSwitcherInteractor.isSimpleUserSwitcher) &&
                 isShadeEnabled &&
                 disableFlags.isQuickSettingsEnabled() &&
                 !isDozing
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BackDropView.java b/packages/SystemUI/src/com/android/systemui/statusbar/BackDropView.java
deleted file mode 100644
index f1eb9fe..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BackDropView.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.FrameLayout;
-
-/**
- * A view who contains media artwork.
- */
-public class BackDropView extends FrameLayout
-{
-    private Runnable mOnVisibilityChangedRunnable;
-
-    public BackDropView(Context context) {
-        super(context);
-    }
-
-    public BackDropView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public BackDropView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    public BackDropView(Context context, AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-    }
-
-    @Override
-    public boolean hasOverlappingRendering() {
-        return false;
-    }
-
-    @Override
-    protected void onVisibilityChanged(View changedView, int visibility) {
-        super.onVisibilityChanged(changedView, visibility);
-        if (changedView == this && mOnVisibilityChangedRunnable != null) {
-            mOnVisibilityChangedRunnable.run();
-        }
-    }
-
-    public void setOnVisibilityChangedRunnable(Runnable runnable) {
-        mOnVisibilityChangedRunnable = runnable;
-    }
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index f32f1c2..710e59d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -21,6 +21,7 @@
 import static android.os.UserHandle.USER_NULL;
 import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
 import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
+import static android.os.Flags.allowPrivateProfile;
 
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 
@@ -79,6 +80,7 @@
 import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.Executor;
+import java.util.Objects;
 
 import javax.inject.Inject;
 
@@ -177,57 +179,50 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
-            switch (action) {
-                case Intent.ACTION_USER_REMOVED:
-                    int removedUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
-                    if (removedUserId != -1) {
-                        for (UserChangedListener listener : mListeners) {
-                            listener.onUserRemoved(removedUserId);
-                        }
+            if (Objects.equals(action, Intent.ACTION_USER_REMOVED)) {
+                int removedUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+                if (removedUserId != -1) {
+                    for (UserChangedListener listener : mListeners) {
+                        listener.onUserRemoved(removedUserId);
                     }
-                    updateCurrentProfilesCache();
-                    break;
-                case Intent.ACTION_USER_ADDED:
-                    updateCurrentProfilesCache();
-                    if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
-                        final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
-                        mBackgroundHandler.post(() -> {
-                            initValuesForUser(userId);
-                        });
+                }
+                updateCurrentProfilesCache();
+            } else if (Objects.equals(action, Intent.ACTION_USER_ADDED)){
+                updateCurrentProfilesCache();
+                if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+                    final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
+                    mBackgroundHandler.post(() -> {
+                        initValuesForUser(userId);
+                    });
+                }
+            } else if (profileAvailabilityActions(action)) {
+                updateCurrentProfilesCache();
+            } else if (Objects.equals(action, Intent.ACTION_USER_UNLOCKED)) {
+                // Start the overview connection to the launcher service
+                // Connect if user hasn't connected yet
+                if (mOverviewProxyServiceLazy.get().getProxy() == null) {
+                    mOverviewProxyServiceLazy.get().startConnectionToCurrentUser();
+                }
+            } else if (Objects.equals(action, NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION)) {
+                final IntentSender intentSender = intent.getParcelableExtra(
+                        Intent.EXTRA_INTENT);
+                final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX);
+                if (intentSender != null) {
+                    try {
+                        ActivityOptions options = ActivityOptions.makeBasic();
+                        options.setPendingIntentBackgroundActivityStartMode(
+                                ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+                        mContext.startIntentSender(intentSender, null, 0, 0, 0,
+                                options.toBundle());
+                    } catch (IntentSender.SendIntentException e) {
+                        /* ignore */
                     }
-                    break;
-                case Intent.ACTION_MANAGED_PROFILE_AVAILABLE:
-                case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE:
-                    updateCurrentProfilesCache();
-                    break;
-                case Intent.ACTION_USER_UNLOCKED:
-                    // Start the overview connection to the launcher service
-                    // Connect if user hasn't connected yet
-                    if (mOverviewProxyServiceLazy.get().getProxy() == null) {
-                        mOverviewProxyServiceLazy.get().startConnectionToCurrentUser();
-                    }
-                    break;
-                case NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION:
-                    final IntentSender intentSender = intent.getParcelableExtra(
-                            Intent.EXTRA_INTENT);
-                    final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX);
-                    if (intentSender != null) {
-                        try {
-                            ActivityOptions options = ActivityOptions.makeBasic();
-                            options.setPendingIntentBackgroundActivityStartMode(
-                                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
-                            mContext.startIntentSender(intentSender, null, 0, 0, 0,
-                                    options.toBundle());
-                        } catch (IntentSender.SendIntentException e) {
-                            /* ignore */
-                        }
-                    }
-                    if (notificationKey != null) {
-                        final NotificationVisibility nv = mVisibilityProviderLazy.get()
-                                .obtain(notificationKey, true);
-                        mClickNotifier.onNotificationClick(notificationKey, nv);
-                    }
-                    break;
+                }
+                if (notificationKey != null) {
+                    final NotificationVisibility nv = mVisibilityProviderLazy.get()
+                            .obtain(notificationKey, true);
+                    mClickNotifier.onNotificationClick(notificationKey, nv);
+                }
             }
         }
     };
@@ -403,6 +398,10 @@
         filter.addAction(Intent.ACTION_USER_UNLOCKED);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
+        if (allowPrivateProfile()){
+            filter.addAction(Intent.ACTION_PROFILE_AVAILABLE);
+            filter.addAction(Intent.ACTION_PROFILE_UNAVAILABLE);
+        }
         mBroadcastDispatcher.registerReceiver(mBaseBroadcastReceiver, filter,
                 null /* executor */, UserHandle.ALL);
 
@@ -814,6 +813,14 @@
         }
     }
 
+    private boolean profileAvailabilityActions(String action){
+        return allowPrivateProfile()?
+                Objects.equals(action,Intent.ACTION_PROFILE_AVAILABLE)||
+                        Objects.equals(action,Intent.ACTION_PROFILE_UNAVAILABLE):
+                Objects.equals(action,Intent.ACTION_MANAGED_PROFILE_AVAILABLE)||
+                        Objects.equals(action,Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
+    }
+
     @Override
     public void dump(PrintWriter pw, String[] args) {
         pw.println("NotificationLockscreenUserManager state:");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 389486f..9c4625e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -18,31 +18,21 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Notification;
-import android.app.WallpaperManager;
 import android.content.Context;
-import android.graphics.Point;
 import android.graphics.drawable.Icon;
-import android.hardware.display.DisplayManager;
 import android.media.MediaMetadata;
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
 import android.media.session.PlaybackState;
-import android.os.Trace;
 import android.service.notification.NotificationStats;
 import android.service.notification.StatusBarNotification;
 import android.util.Log;
-import android.view.Display;
-import android.view.View;
-import android.widget.ImageView;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dumpable;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.media.controls.models.player.MediaData;
 import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData;
 import com.android.systemui.media.controls.pipeline.MediaDataManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.dagger.CentralSurfacesModule;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -50,21 +40,13 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.phone.BiometricUnlockController;
-import com.android.systemui.statusbar.phone.LockscreenWallpaper;
-import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
-import java.util.Comparator;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
-import java.util.stream.Collectors;
 
 /**
  * Handles tasks and state related to media notifications. For example, there is a 'current' media
@@ -74,9 +56,6 @@
     private static final String TAG = "NotificationMediaManager";
     public static final boolean DEBUG_MEDIA = false;
 
-    private final StatusBarStateController mStatusBarStateController;
-    private final SysuiColorExtractor mColorExtractor;
-    private final KeyguardStateController mKeyguardStateController;
     private static final HashSet<Integer> PAUSED_MEDIA_STATES = new HashSet<>();
     private static final HashSet<Integer> CONNECTING_MEDIA_STATES = new HashSet<>();
     static {
@@ -93,15 +72,6 @@
     private final NotifPipeline mNotifPipeline;
     private final NotifCollection mNotifCollection;
 
-    @Nullable
-    private BiometricUnlockController mBiometricUnlockController;
-    @Nullable
-    private ScrimController mScrimController;
-    @Nullable
-    private LockscreenWallpaper mLockscreenWallpaper;
-    @VisibleForTesting
-    boolean mIsLockscreenLiveWallpaperEnabled;
-
     private final Context mContext;
     private final ArrayList<MediaListener> mMediaListeners;
 
@@ -110,16 +80,6 @@
     private String mMediaNotificationKey;
     private MediaMetadata mMediaMetadata;
 
-    private BackDropView mBackdrop;
-    private ImageView mBackdropFront;
-    private ImageView mBackdropBack;
-    private final Point mTmpDisplaySize = new Point();
-
-    private final DisplayManager mDisplayManager;
-    @Nullable
-    private List<String> mSmallerInternalDisplayUids;
-    private Display mCurrentDisplay;
-
     private final MediaController.Callback mMediaListener = new MediaController.Callback() {
         @Override
         public void onPlaybackStateChanged(PlaybackState state) {
@@ -142,7 +102,7 @@
                 Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata);
             }
             mMediaMetadata = metadata;
-            dispatchUpdateMediaMetaData(true /* changed */, true /* allowAnimation */);
+            dispatchUpdateMediaMetaData();
         }
     };
 
@@ -155,23 +115,13 @@
             NotifPipeline notifPipeline,
             NotifCollection notifCollection,
             MediaDataManager mediaDataManager,
-            StatusBarStateController statusBarStateController,
-            SysuiColorExtractor colorExtractor,
-            KeyguardStateController keyguardStateController,
-            DumpManager dumpManager,
-            WallpaperManager wallpaperManager,
-            DisplayManager displayManager) {
+            DumpManager dumpManager) {
         mContext = context;
         mMediaListeners = new ArrayList<>();
         mVisibilityProvider = visibilityProvider;
         mMediaDataManager = mediaDataManager;
         mNotifPipeline = notifPipeline;
         mNotifCollection = notifCollection;
-        mStatusBarStateController = statusBarStateController;
-        mColorExtractor = colorExtractor;
-        mKeyguardStateController = keyguardStateController;
-        mDisplayManager = displayManager;
-        mIsLockscreenLiveWallpaperEnabled = wallpaperManager.isLockscreenLiveWallpaperEnabled();
 
         setupNotifPipeline();
 
@@ -275,7 +225,7 @@
     public void onNotificationRemoved(String key) {
         if (key.equals(mMediaNotificationKey)) {
             clearCurrentMediaNotification();
-            dispatchUpdateMediaMetaData(true /* changed */, true /* allowEnterAnimation */);
+            dispatchUpdateMediaMetaData();
         }
     }
 
@@ -309,21 +259,18 @@
     }
 
     public void findAndUpdateMediaNotifications() {
-        boolean metaDataChanged;
         // TODO(b/169655907): get the semi-filtered notifications for current user
         Collection<NotificationEntry> allNotifications = mNotifPipeline.getAllNotifs();
-        metaDataChanged = findPlayingMediaNotification(allNotifications);
-        dispatchUpdateMediaMetaData(metaDataChanged, true /* allowEnterAnimation */);
+        findPlayingMediaNotification(allNotifications);
+        dispatchUpdateMediaMetaData();
     }
 
     /**
      * Find a notification and media controller associated with the playing media session, and
      * update this manager's internal state.
-     * @return whether the current MediaMetadata changed (and needs to be announced to listeners).
+     * TODO(b/273443374) check this method
      */
-    boolean findPlayingMediaNotification(
-            @NonNull Collection<NotificationEntry> allNotifications) {
-        boolean metaDataChanged = false;
+    void findPlayingMediaNotification(@NonNull Collection<NotificationEntry> allNotifications) {
         // Promote the media notification with a controller in 'playing' state, if any.
         NotificationEntry mediaNotification = null;
         MediaController controller = null;
@@ -359,8 +306,6 @@
                 Log.v(TAG, "DEBUG_MEDIA: insert listener, found new controller: "
                         + mMediaController + ", receive metadata: " + mMediaMetadata);
             }
-
-            metaDataChanged = true;
         }
 
         if (mediaNotification != null
@@ -371,8 +316,6 @@
                         + mMediaNotificationKey);
             }
         }
-
-        return metaDataChanged;
     }
 
     public void clearCurrentMediaNotification() {
@@ -380,10 +323,7 @@
         clearCurrentMediaNotificationSession();
     }
 
-    private void dispatchUpdateMediaMetaData(boolean changed, boolean allowEnterAnimation) {
-        if (mPresenter != null) {
-            mPresenter.updateMediaMetaData(changed, allowEnterAnimation);
-        }
+    private void dispatchUpdateMediaMetaData() {
         @PlaybackState.State int state = getMediaControllerPlaybackState(mMediaController);
         ArrayList<MediaListener> callbacks = new ArrayList<>(mMediaListeners);
         for (int i = 0; i < callbacks.size(); i++) {
@@ -446,125 +386,6 @@
         mMediaController = null;
     }
 
-    /**
-     * Notify lockscreen wallpaper drawable the current internal display.
-     */
-    public void onDisplayUpdated(Display display) {
-        Trace.beginSection("NotificationMediaManager#onDisplayUpdated");
-        mCurrentDisplay = display;
-        Trace.endSection();
-    }
-
-    private boolean isOnSmallerInternalDisplays() {
-        if (mSmallerInternalDisplayUids == null) {
-            mSmallerInternalDisplayUids = findSmallerInternalDisplayUids();
-        }
-        return mSmallerInternalDisplayUids.contains(mCurrentDisplay.getUniqueId());
-    }
-
-    private List<String> findSmallerInternalDisplayUids() {
-        if (mSmallerInternalDisplayUids != null) {
-            return mSmallerInternalDisplayUids;
-        }
-        List<Display> internalDisplays = Arrays.stream(mDisplayManager.getDisplays(
-                        DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED))
-                .filter(display -> display.getType() == Display.TYPE_INTERNAL)
-                .collect(Collectors.toList());
-        if (internalDisplays.isEmpty()) {
-            return List.of();
-        }
-        Display largestDisplay = internalDisplays.stream()
-                .max(Comparator.comparingInt(this::getRealDisplayArea))
-                .orElse(internalDisplays.get(0));
-        internalDisplays.remove(largestDisplay);
-        return internalDisplays.stream().map(Display::getUniqueId).collect(Collectors.toList());
-    }
-
-    private int getRealDisplayArea(Display display) {
-        display.getRealSize(mTmpDisplaySize);
-        return mTmpDisplaySize.x * mTmpDisplaySize.y;
-    }
-
-    /**
-     * Update media state of lockscreen media views and controllers .
-     */
-    public void updateMediaMetaData(boolean metaDataChanged) {
-
-        if (mIsLockscreenLiveWallpaperEnabled) return;
-
-        Trace.beginSection("CentralSurfaces#updateMediaMetaData");
-        if (getBackDropView() == null) {
-            Trace.endSection();
-            return; // called too early
-        }
-
-        boolean wakeAndUnlock = mBiometricUnlockController != null
-            && mBiometricUnlockController.isWakeAndUnlock();
-        if (mKeyguardStateController.isLaunchTransitionFadingAway() || wakeAndUnlock) {
-            mBackdrop.setVisibility(View.INVISIBLE);
-            Trace.endSection();
-            return;
-        }
-
-        MediaMetadata mediaMetadata = getMediaMetadata();
-
-        if (DEBUG_MEDIA) {
-            Log.v(TAG, "DEBUG_MEDIA: updating album art for notification "
-                    + getMediaNotificationKey()
-                    + " metadata=" + mediaMetadata
-                    + " metaDataChanged=" + metaDataChanged
-                    + " state=" + mStatusBarStateController.getState());
-        }
-
-        mColorExtractor.setHasMediaArtwork(false);
-        if (mScrimController != null) {
-            mScrimController.setHasBackdrop(false);
-        }
-
-        Trace.endSection();
-    }
-
-    public void setup(BackDropView backdrop, ImageView backdropFront, ImageView backdropBack,
-            ScrimController scrimController, LockscreenWallpaper lockscreenWallpaper) {
-        mBackdrop = backdrop;
-        mBackdropFront = backdropFront;
-        mBackdropBack = backdropBack;
-        mScrimController = scrimController;
-        mLockscreenWallpaper = lockscreenWallpaper;
-    }
-
-    public void setBiometricUnlockController(BiometricUnlockController biometricUnlockController) {
-        mBiometricUnlockController = biometricUnlockController;
-    }
-
-    /**
-     * Hide the album artwork that is fading out and release its bitmap.
-     */
-    protected final Runnable mHideBackdropFront = new Runnable() {
-        @Override
-        public void run() {
-            if (DEBUG_MEDIA) {
-                Log.v(TAG, "DEBUG_MEDIA: removing fade layer");
-            }
-            mBackdropFront.setVisibility(View.INVISIBLE);
-            mBackdropFront.animate().cancel();
-            mBackdropFront.setImageDrawable(null);
-        }
-    };
-
-    // TODO(b/273443374): remove
-    public boolean isLockscreenWallpaperOnNotificationShade() {
-        return mBackdrop != null && mLockscreenWallpaper != null
-                && !mLockscreenWallpaper.isLockscreenLiveWallpaperEnabled()
-                && (mBackdropFront.isVisibleToUser() || mBackdropBack.isVisibleToUser());
-    }
-
-    // TODO(b/273443374) temporary test helper; remove
-    @VisibleForTesting
-    BackDropView getBackDropView() {
-        return mBackdrop;
-    }
-
     public interface MediaListener {
         /**
          * Called whenever there's new metadata or playback state.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
index 5dcf6d1..f3b5ab6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
@@ -32,11 +32,6 @@
     boolean isPresenterFullyCollapsed();
 
     /**
-     * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper.
-     */
-    void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation);
-
-    /**
      * Called when the current user changes.
      * @param newUserId new user id
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
index 17da015..ffde8c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
@@ -118,16 +118,27 @@
 
         // If the view is being removed, this may be called even though we're not active
         boolean remoteInputActiveForEntry = isRemoteInputActive(entry);
+        boolean remoteInputActive = isRemoteInputActive();
         mLogger.logRemoveRemoteInput(
                 entry.getKey() /* entryKey */,
                 entry.mRemoteEditImeVisible /* remoteEditImeVisible */,
                 entry.mRemoteEditImeAnimatingAway /* remoteEditImeAnimatingAway */,
                 remoteInputActiveForEntry /* isRemoteInputActiveForEntry */,
-                isRemoteInputActive()/* isRemoteInputActive */,
+                remoteInputActive/* isRemoteInputActive */,
                 reason/* reason */,
                 entry.getNotificationStyle()/* notificationStyle */);
 
-        if (!remoteInputActiveForEntry) return;
+        if (!remoteInputActiveForEntry) {
+            if (mLastAppliedRemoteInputActive != null
+                    && mLastAppliedRemoteInputActive
+                    && !remoteInputActive) {
+                mLogger.logRemoteInputApplySkipped(
+                        entry.getKey() /* entryKey */,
+                        reason/* reason */,
+                        entry.getNotificationStyle()/* notificationStyle */);
+            }
+            return;
+        }
 
         pruneWeakThenRemoveAndContains(null /* contains */, entry /* remove */, token);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 125c8efe..1fe6b83 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -16,9 +16,7 @@
 
 package com.android.systemui.statusbar.dagger;
 
-import android.app.WallpaperManager;
 import android.content.Context;
-import android.hardware.display.DisplayManager;
 import android.os.RemoteException;
 import android.service.dreams.IDreamManager;
 import android.util.Log;
@@ -29,7 +27,6 @@
 import com.android.systemui.animation.AnimationFeatureFlags;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpHandler;
 import com.android.systemui.dump.DumpManager;
@@ -121,24 +118,14 @@
             NotifPipeline notifPipeline,
             NotifCollection notifCollection,
             MediaDataManager mediaDataManager,
-            StatusBarStateController statusBarStateController,
-            SysuiColorExtractor colorExtractor,
-            KeyguardStateController keyguardStateController,
-            DumpManager dumpManager,
-            WallpaperManager wallpaperManager,
-            DisplayManager displayManager) {
+            DumpManager dumpManager) {
         return new NotificationMediaManager(
                 context,
                 visibilityProvider,
                 notifPipeline,
                 notifCollection,
                 mediaDataManager,
-                statusBarStateController,
-                colorExtractor,
-                keyguardStateController,
-                dumpManager,
-                wallpaperManager,
-                displayManager);
+                dumpManager);
     }
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
index bde298d..ea1d782 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
@@ -36,6 +36,11 @@
     val showAnimation: Boolean
     val viewCreator: ViewCreator
     var contentDescription: String?
+    /**
+     * When true, an accessibility event with [contentDescription] is announced when the view
+     * becomes visible.
+     */
+    val shouldAnnounceAccessibilityEvent: Boolean
 
     // Update this event with values from another event.
     fun updateFromEvent(other: StatusEvent?) {
@@ -76,6 +81,7 @@
     override var forceVisible = false
     override val showAnimation = true
     override var contentDescription: String? = ""
+    override val shouldAnnounceAccessibilityEvent: Boolean = false
 
     override val viewCreator: ViewCreator = { context ->
         BatteryStatusChip(context).apply {
@@ -95,6 +101,7 @@
     override var forceVisible = false
     override val showAnimation = true
     override var contentDescription: String? = ""
+    override val shouldAnnounceAccessibilityEvent: Boolean = true
 
     override val viewCreator: ViewCreator = { context ->
         ConnectedDisplayChip(context)
@@ -110,6 +117,7 @@
     override var contentDescription: String? = null
     override val priority = 100
     override var forceVisible = true
+    override val shouldAnnounceAccessibilityEvent: Boolean = false
     var privacyItems: List<PrivacyItem> = listOf()
     private var privacyChip: OngoingPrivacyChip? = null
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
index dccc23f..73c0bfe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
@@ -273,6 +273,11 @@
         })
     }
 
+    /** Announces [contentDescriptions] for accessibility. */
+    fun announceForAccessibility(contentDescriptions: String) {
+        currentAnimatedView?.view?.announceForAccessibility(contentDescriptions)
+    }
+
     private fun updateDimens(contentArea: Rect) {
         val lp = animationWindowView.layoutParams as FrameLayout.LayoutParams
         lp.height = contentArea.height()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
index 7d866df..8ee1ade 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
@@ -100,9 +100,12 @@
     }
 
     private fun startConnectedDisplayCollection() {
+        val connectedDisplayEvent = ConnectedDisplayEvent().apply {
+            contentDescription = context.getString(R.string.connected_display_icon_desc)
+        }
         connectedDisplayCollectionJob =
                 onDisplayConnectedFlow
-                        .onEach { scheduler.onStatusEvent(ConnectedDisplayEvent()) }
+                        .onEach { scheduler.onStatusEvent(connectedDisplayEvent) }
                         .launchIn(appScope)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
index a3bc002..f0e60dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
@@ -254,11 +254,18 @@
         currentlyRunningAnimationJob =
             coroutineScope.launch {
                 runChipAppearAnimation()
+                announceForAccessibilityIfNeeded(event)
                 delay(APPEAR_ANIMATION_DURATION + DISPLAY_LENGTH)
                 runChipDisappearAnimation()
             }
     }
 
+    private fun announceForAccessibilityIfNeeded(event: StatusEvent) {
+        val description = event.contentDescription ?: return
+        if (!event.shouldAnnounceAccessibilityEvent)  return
+        chipAnimationController.announceForAccessibility(description)
+    }
+
     /**
      * 1. Define a total budget for the chip animation (1500ms)
      * 2. Send out callbacks to listeners so that they can generate animations locally
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
index 57d20246..8957f29 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
@@ -114,18 +114,21 @@
                 || previousAnimator.getAnimatedFraction() == 0)) {
             animator.setStartDelay(properties.delay);
         }
-        if (listener != null) {
-            animator.addListener(listener);
-        }
         // remove the tag when the animation is finished
         animator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
-                view.setTag(animatorTag, null);
-                view.setTag(animationStartTag, null);
-                view.setTag(animationEndTag, null);
+                Animator existing = (Animator) view.getTag(animatorTag);
+                if (existing == animation) {
+                    view.setTag(animatorTag, null);
+                    view.setTag(animationStartTag, null);
+                    view.setTag(animationEndTag, null);
+                }
             }
         });
+        if (listener != null) {
+            animator.addListener(listener);
+        }
         ViewState.startAnimator(animator, listener);
         view.setTag(animatorTag, animator);
         view.setTag(animationStartTag, currentValue);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
index ff89c62..23f87ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
@@ -83,6 +83,21 @@
             }
         )
 
+    fun logRemoteInputApplySkipped(entryKey: String, reason: String, notificationStyle: String) =
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                str1 = entryKey
+                str2 = reason
+                str3 = notificationStyle
+            },
+            {
+                "removeRemoteInput[apply is skipped] reason: $str2" +
+                    "for entry: $str1, style: $str3 "
+            }
+        )
+
     private companion object {
         private const val TAG = "RemoteInputControllerLog"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 4573d59..cfe9fbe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -748,7 +748,11 @@
         return row != null && row.getGuts() != null && row.getGuts().isExposed();
     }
 
-    public boolean isChildInGroup() {
+    /**
+     * @return Whether the notification row is a child of a group notification view; false if the
+     * row is null
+     */
+    public boolean rowIsChildInGroup() {
         return row != null && row.isChildInGroup();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
index 9ba1f7a..380cdad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import dagger.Binds
 import dagger.Module
 import javax.inject.Inject
@@ -52,7 +53,8 @@
     private val lockscreenUserManager: NotificationLockscreenUserManager,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val statusBarStateController: StatusBarStateController,
-    private val keyguardStateController: KeyguardStateController
+    private val keyguardStateController: KeyguardStateController,
+    private val selectedUserInteractor: SelectedUserInteractor,
 ) : Invalidator("SensitiveContentInvalidator"),
         SensitiveContentCoordinator,
         DynamicPrivacyController.Listener,
@@ -67,10 +69,10 @@
     override fun onDynamicPrivacyChanged(): Unit = invalidateList("onDynamicPrivacyChanged")
 
     override fun onBeforeRenderList(entries: List<ListEntry>) {
-        if (keyguardStateController.isKeyguardGoingAway() ||
-                statusBarStateController.getState() == StatusBarState.KEYGUARD &&
+        if (keyguardStateController.isKeyguardGoingAway ||
+                statusBarStateController.state == StatusBarState.KEYGUARD &&
                 keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(
-                        KeyguardUpdateMonitor.getCurrentUser())) {
+                        selectedUserInteractor.getSelectedUserId())) {
             // don't update yet if:
             // - the keyguard is currently going away
             // - LS is about to be dismissed by a biometric that bypasses LS (avoid notif flash)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index f750fed..1229cb9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -31,6 +31,7 @@
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
 
 /** View-model for the shared notification container, used by both the shade and keyguard spaces */
 class SharedNotificationContainerViewModel
@@ -45,8 +46,8 @@
 ) {
     private val statesForConstrainedNotifications =
         setOf(
-            KeyguardState.LOCKSCREEN,
             KeyguardState.AOD,
+            KeyguardState.LOCKSCREEN,
             KeyguardState.DOZING,
             KeyguardState.ALTERNATE_BOUNCER,
             KeyguardState.PRIMARY_BOUNCER
@@ -68,8 +69,17 @@
 
     /** If the user is visually on one of the unoccluded lockscreen states. */
     val isOnLockscreen: Flow<Boolean> =
-        keyguardTransitionInteractor.finishedKeyguardState
-            .map { statesForConstrainedNotifications.contains(it) }
+        combine(
+                keyguardTransitionInteractor.finishedKeyguardState.map {
+                    statesForConstrainedNotifications.contains(it)
+                },
+                keyguardTransitionInteractor
+                    .transitionValue(KeyguardState.LOCKSCREEN)
+                    .onStart { emit(0f) }
+                    .map { it > 0 }
+            ) { constrainedNotificationState, transitioningToOrFromLockscreen ->
+                constrainedNotificationState || transitioningToOrFromLockscreen
+            }
             .distinctUntilChanged()
 
     /** Are we purely on the keyguard without the shade/qs? */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 8129b83..daa4f18 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -52,6 +52,7 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.domain.interactor.BiometricUnlockInteractor;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.res.R;
@@ -59,8 +60,11 @@
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.time.SystemClock;
 
+import dagger.Lazy;
+
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -172,9 +176,11 @@
     private final WakefulnessLifecycle mWakefulnessLifecycle;
     private final LatencyTracker mLatencyTracker;
     private final VibratorHelper mVibratorHelper;
+    private final BiometricUnlockInteractor mBiometricUnlockInteractor;
     private final BiometricUnlockLogger mLogger;
     private final SystemClock mSystemClock;
     private final boolean mOrderUnlockAndWake;
+    private final Lazy<SelectedUserInteractor> mSelectedUserInteractor;
     private final DeviceEntryHapticsInteractor mHapticsInteractor;
 
     private long mLastFpFailureUptimeMillis;
@@ -286,7 +292,9 @@
             VibratorHelper vibrator,
             SystemClock systemClock,
             FeatureFlags featureFlags,
-            DeviceEntryHapticsInteractor hapticsInteractor
+            DeviceEntryHapticsInteractor hapticsInteractor,
+            Lazy<SelectedUserInteractor> selectedUserInteractor,
+            BiometricUnlockInteractor biometricUnlockInteractor
     ) {
         mPowerManager = powerManager;
         mUpdateMonitor = keyguardUpdateMonitor;
@@ -295,6 +303,7 @@
         mLatencyTracker = latencyTracker;
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
+        mBiometricUnlockInteractor = biometricUnlockInteractor;
 
         mNotificationShadeWindowController = notificationShadeWindowController;
         mDozeScrimController = dozeScrimController;
@@ -317,6 +326,7 @@
         mOrderUnlockAndWake = resources.getBoolean(
                 com.android.internal.R.bool.config_orderUnlockAndWake);
         mHapticsInteractor = hapticsInteractor;
+        mSelectedUserInteractor = selectedUserInteractor;
 
         dumpManager.registerDumpable(this);
     }
@@ -501,7 +511,6 @@
             case MODE_WAKE_AND_UNLOCK:
                 if (mMode == MODE_WAKE_AND_UNLOCK_PULSING) {
                     Trace.beginSection("MODE_WAKE_AND_UNLOCK_PULSING");
-                    mMediaManager.updateMediaMetaData(false /* metaDataChanged */);
                 } else if (mMode == MODE_WAKE_AND_UNLOCK){
                     Trace.beginSection("MODE_WAKE_AND_UNLOCK");
                 } else {
@@ -525,6 +534,7 @@
         for (BiometricUnlockEventsListener listener : mBiometricUnlockEventsListeners) {
             listener.onModeChanged(mode);
         }
+        mBiometricUnlockInteractor.setBiometricUnlockState(mode);
     }
 
     private void onBiometricUnlockedWithKeyguardDismissal(BiometricSourceType biometricSourceType) {
@@ -537,7 +547,8 @@
         return mPendingAuthenticated != null
                 && mUpdateMonitor
                     .isUnlockingWithBiometricAllowed(mPendingAuthenticated.isStrongBiometric)
-                && mPendingAuthenticated.userId == KeyguardUpdateMonitor.getCurrentUser();
+                && mPendingAuthenticated.userId
+                    == mSelectedUserInteractor.get().getSelectedUserId();
     }
 
     public @WakeAndUnlockMode int getMode() {
@@ -601,11 +612,11 @@
             // if unlocking isn't allowed, log more information about why unlocking may not
             // have been allowed
             final int strongAuthFlags = mUpdateMonitor.getStrongAuthTracker().getStrongAuthForUser(
-                    KeyguardUpdateMonitor.getCurrentUser());
+                    mSelectedUserInteractor.get().getSelectedUserId());
             final boolean nonStrongBiometricAllowed =
                     mUpdateMonitor.getStrongAuthTracker()
                             .isNonStrongBiometricAllowedAfterIdleTimeout(
-                                    KeyguardUpdateMonitor.getCurrentUser());
+                                    mSelectedUserInteractor.get().getSelectedUserId());
 
             mLogger.logCalculateModeForFingerprintUnlockingNotAllowed(strongBiometric,
                     strongAuthFlags, nonStrongBiometricAllowed, deviceInteractive, keyguardShowing);
@@ -671,11 +682,11 @@
             // if unlocking isn't allowed, log more information about why unlocking may not
             // have been allowed
             final int strongAuthFlags = mUpdateMonitor.getStrongAuthTracker().getStrongAuthForUser(
-                    KeyguardUpdateMonitor.getCurrentUser());
+                    mSelectedUserInteractor.get().getSelectedUserId());
             final boolean nonStrongBiometricAllowed =
                     mUpdateMonitor.getStrongAuthTracker()
                             .isNonStrongBiometricAllowedAfterIdleTimeout(
-                                    KeyguardUpdateMonitor.getCurrentUser());
+                                    mSelectedUserInteractor.get().getSelectedUserId());
 
             mLogger.logCalculateModeForPassiveAuthUnlockingNotAllowed(
                     strongBiometric, strongAuthFlags, nonStrongBiometricAllowed,
@@ -723,7 +734,7 @@
         // Suppress all face auth errors if fingerprint can be used to authenticate
         if ((biometricSourceType == BiometricSourceType.FACE
                 && !mUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                KeyguardUpdateMonitor.getCurrentUser()))
+                mSelectedUserInteractor.get().getSelectedUserId()))
                 || (biometricSourceType == BiometricSourceType.FINGERPRINT)) {
             mHapticsInteractor.vibrateError();
         }
@@ -781,6 +792,7 @@
         for (BiometricUnlockEventsListener listener : mBiometricUnlockEventsListeners) {
             listener.onResetMode();
         }
+        mBiometricUnlockInteractor.setBiometricUnlockState(MODE_NONE);
         mNumConsecutiveFpFailures = 0;
         mLastFpFailureUptimeMillis = 0;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 9fb6c1b..7cd32f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -76,7 +76,6 @@
 import android.util.EventLog;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
-import android.util.MathUtils;
 import android.view.Display;
 import android.view.IRemoteAnimationRunner;
 import android.view.IWindowManager;
@@ -176,7 +175,6 @@
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.statusbar.AutoHideUiElement;
-import com.android.systemui.statusbar.BackDropView;
 import com.android.systemui.statusbar.CircleReveal;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.GestureRecorder;
@@ -237,10 +235,10 @@
 import com.android.wm.shell.startingsurface.SplashscreenContentDrawer;
 import com.android.wm.shell.startingsurface.StartingSurface;
 
-import dagger.Lazy;
-
 import dalvik.annotation.optimization.NeverCompile;
 
+import dagger.Lazy;
+
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.List;
@@ -376,9 +374,6 @@
     private boolean mBrightnessMirrorVisible;
     private BiometricUnlockController mBiometricUnlockController;
     private final LightBarController mLightBarController;
-    private final Lazy<LockscreenWallpaper> mLockscreenWallpaperLazy;
-    @Nullable
-    protected LockscreenWallpaper mLockscreenWallpaper;
     private final AutoHideController mAutoHideController;
 
     private final Point mCurrentDisplaySize = new Point();
@@ -658,7 +653,6 @@
             NotificationExpansionRepository notificationExpansionRepository,
             DozeParameters dozeParameters,
             ScrimController scrimController,
-            Lazy<LockscreenWallpaper> lockscreenWallpaperLazy,
             Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
             AuthRippleController authRippleController,
             DozeServiceHost dozeServiceHost,
@@ -770,7 +764,6 @@
         mPowerManager = powerManager;
         mDozeParameters = dozeParameters;
         mScrimController = scrimController;
-        mLockscreenWallpaperLazy = lockscreenWallpaperLazy;
         mDozeScrimController = dozeScrimController;
         mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
         mAuthRippleController = authRippleController;
@@ -1198,10 +1191,6 @@
 
         createNavigationBar(result);
 
-        if (ENABLE_LOCKSCREEN_WALLPAPER && mWallpaperSupported) {
-            mLockscreenWallpaper = mLockscreenWallpaperLazy.get();
-        }
-
         mAmbientIndicationContainer = getNotificationShadeWindowView().findViewById(
                 R.id.ambient_indication_container);
 
@@ -1268,24 +1257,6 @@
                 mNotificationShelfController,
                 mHeadsUpManager);
 
-        BackDropView backdrop = getNotificationShadeWindowView().findViewById(R.id.backdrop);
-        if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
-            mMediaManager.setup(null, null, null, mScrimController, null);
-        } else {
-            mMediaManager.setup(backdrop, backdrop.findViewById(R.id.backdrop_front),
-                    backdrop.findViewById(R.id.backdrop_back), mScrimController,
-                    mLockscreenWallpaper);
-        }
-        float maxWallpaperZoom = mContext.getResources().getFloat(
-                com.android.internal.R.dimen.config_wallpaperMaxScale);
-        mNotificationShadeDepthControllerLazy.get().addListener(depth -> {
-            float scale = MathUtils.lerp(maxWallpaperZoom, 1f, depth);
-            backdrop.setPivotX(backdrop.getWidth() / 2f);
-            backdrop.setPivotY(backdrop.getHeight() / 2f);
-            backdrop.setScaleX(scale);
-            backdrop.setScaleY(scale);
-        });
-
         // Set up the quick settings tile panel
         final View container = getNotificationShadeWindowView().findViewById(R.id.qs_frame);
         if (container != null) {
@@ -1357,14 +1328,6 @@
         // receive broadcasts
         registerBroadcastReceiver();
 
-        IntentFilter demoFilter = new IntentFilter();
-        if (DEBUG_MEDIA_FAKE_ARTWORK) {
-            demoFilter.addAction(ACTION_FAKE_ARTWORK);
-        }
-        mContext.registerReceiverAsUser(mDemoReceiver, UserHandle.ALL, demoFilter,
-                android.Manifest.permission.DUMP, null,
-                Context.RECEIVER_EXPORTED_UNAUDITED);
-
         // listen for USER_SETUP_COMPLETE setting (per-user)
         mDeviceProvisionedController.addCallback(mUserSetupObserver);
         mUserSetupObserver.onUserSetupChanged();
@@ -1525,6 +1488,7 @@
         // regressions, we'll continue standing up the root view in CentralSurfaces.
         mNotificationShadeWindowController.fetchWindowRootView();
         getNotificationShadeWindowViewController().setupExpandedStatusBar();
+        getNotificationShadeWindowViewController().setupCommunalHubLayout();
         mShadeController.setNotificationShadeWindowViewController(
                 getNotificationShadeWindowViewController());
         mBackActionInteractor.setup(mQsController, mShadeSurface);
@@ -1582,7 +1546,6 @@
         mRemoteInputManager.addControllerCallback(mStatusBarKeyguardViewManager);
 
         mLightBarController.setBiometricUnlockController(mBiometricUnlockController);
-        mMediaManager.setBiometricUnlockController(mBiometricUnlockController);
         Trace.endSection();
     }
 
@@ -1869,7 +1832,6 @@
     void updateDisplaySize() {
         mDisplay.getMetrics(mDisplayMetrics);
         mDisplay.getSize(mCurrentDisplaySize);
-        mMediaManager.onDisplayUpdated(mDisplay);
         if (DEBUG_GESTURES) {
             mGestureRec.tag("display",
                     String.format("%dx%d", mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
@@ -1943,19 +1905,6 @@
         }
     };
 
-    private final BroadcastReceiver mDemoReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (DEBUG) Log.v(TAG, "onReceive: " + intent);
-            String action = intent.getAction();
-            if (ACTION_FAKE_ARTWORK.equals(action)) {
-                if (DEBUG_MEDIA_FAKE_ARTWORK) {
-                    mPresenterLazy.get().updateMediaMetaData(true, true);
-                }
-            }
-        }
-    };
-
     /**
      * Reload some of our resources when the configuration changes.
      *
@@ -2138,7 +2087,6 @@
         releaseGestureWakeLock();
         runLaunchTransitionEndRunnable();
         mKeyguardStateController.setLaunchTransitionFadingAway(false);
-        mPresenterLazy.get().updateMediaMetaData(true /* metaDataChanged */, true);
     }
 
     /**
@@ -2162,7 +2110,6 @@
                 beforeFading.run();
             }
             updateScrimController();
-            mPresenterLazy.get().updateMediaMetaData(false, true);
             mShadeSurface.resetAlpha();
             mShadeSurface.fadeOut(
                     FADE_KEYGUARD_START_DELAY, FADE_KEYGUARD_DURATION,
@@ -3221,8 +3168,6 @@
                     updateDozingState();
                     checkBarModes();
                     updateScrimController();
-                    mPresenterLazy.get()
-                            .updateMediaMetaData(false, mState != StatusBarState.KEYGUARD);
                     Trace.endSection();
                 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 7730f7d9..a11cbc3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -37,14 +37,15 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.AlwaysOnDisplayPolicy;
 import com.android.systemui.doze.DozeScreenState;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.keyguard.domain.interactor.DozeInteractor;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
@@ -55,9 +56,7 @@
 import com.android.systemui.unfold.SysUIUnfoldComponent;
 
 import java.io.PrintWriter;
-import java.util.HashSet;
 import java.util.Optional;
-import java.util.Set;
 
 import javax.inject.Inject;
 
@@ -83,12 +82,11 @@
     private final Resources mResources;
     private final BatteryController mBatteryController;
     private final ScreenOffAnimationController mScreenOffAnimationController;
+    private final DozeInteractor mDozeInteractor;
     private final FoldAodAnimationController mFoldAodAnimationController;
     private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
     private final UserTracker mUserTracker;
 
-    private final Set<Callback> mCallbacks = new HashSet<>();
-
     private boolean mDozeAlwaysOn;
     private boolean mControlScreenOffAnimation;
     private boolean mIsQuickPickupEnabled;
@@ -131,7 +129,8 @@
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             ConfigurationController configurationController,
             StatusBarStateController statusBarStateController,
-            UserTracker userTracker) {
+            UserTracker userTracker,
+            DozeInteractor dozeInteractor) {
         mResources = resources;
         mAmbientDisplayConfiguration = ambientDisplayConfiguration;
         mAlwaysOnPolicy = alwaysOnDisplayPolicy;
@@ -144,6 +143,7 @@
         mScreenOffAnimationController = screenOffAnimationController;
         mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
         mUserTracker = userTracker;
+        mDozeInteractor = dozeInteractor;
 
         keyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
         tunerService.addTunable(
@@ -406,20 +406,6 @@
         return mResources.getStringArray(R.array.doze_brightness_sensor_name_posture_mapping);
     }
 
-    /**
-     * Callback to listen for DozeParameter changes.
-     */
-    public void addCallback(Callback callback) {
-        mCallbacks.add(callback);
-    }
-
-    /**
-     * Remove callback that listens for DozeParameter changes.
-     */
-    public void removeCallback(Callback callback) {
-        mCallbacks.remove(callback);
-    }
-
     @Override
     public void onTuningChanged(String key, String newValue) {
         mDozeAlwaysOn = mAmbientDisplayConfiguration.alwaysOnEnabled(mUserTracker.getUserId());
@@ -465,10 +451,9 @@
     }
 
     private void dispatchAlwaysOnEvent() {
-        for (Callback callback : mCallbacks) {
-            callback.onAlwaysOnChange();
-        }
         mScreenOffAnimationController.onAlwaysOnChanged(getAlwaysOn());
+        mDozeInteractor.setAodAvailable(getAlwaysOn());
+
     }
 
     private boolean getPostureSpecificBool(
@@ -485,14 +470,6 @@
         return bool;
     }
 
-    /** Callbacks for doze parameter related information */
-    public interface Callback {
-        /**
-         * Invoked when the value of getAlwaysOn may have changed.
-         */
-        void onAlwaysOnChange();
-    }
-
     private final class SettingsObserver extends ContentObserver {
         private final Uri mQuickPickupGesture =
                 Settings.Secure.getUriFor(Settings.Secure.DOZE_QUICK_PICKUP_GESTURE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 6b4382f73..f4862c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -175,7 +175,7 @@
         if (!hasPinnedHeadsUp() || topEntry == null) {
             return null;
         } else {
-            if (topEntry.isChildInGroup()) {
+            if (topEntry.rowIsChildInGroup()) {
                 final NotificationEntry groupSummary =
                         mGroupMembershipManager.getGroupSummary(topEntry);
                 if (groupSummary != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index bde5c32..109e77e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.Assert
 import com.android.systemui.util.sensors.AsyncSensorManager
 import java.io.PrintWriter
@@ -48,7 +49,8 @@
     private val asyncSensorManager: AsyncSensorManager,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val keyguardFaceAuthInteractor: KeyguardFaceAuthInteractor,
-    private val dumpManager: DumpManager
+    private val dumpManager: DumpManager,
+    private val selectedUserInteractor: SelectedUserInteractor,
 ) : Dumpable, CoreStartable {
 
     private val pickupSensor = asyncSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE)
@@ -115,7 +117,7 @@
         val onKeyguard = keyguardUpdateMonitor.isKeyguardVisible &&
                 !statusBarStateController.isDozing
 
-        val userId = KeyguardUpdateMonitor.getCurrentUser()
+        val userId = selectedUserInteractor.getSelectedUserId()
         val isFaceEnabled = keyguardUpdateMonitor.isFaceAuthEnabledForUser(userId)
         val shouldListen = (onKeyguard || bouncerVisible) && isFaceEnabled
         if (shouldListen != isListening) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 2960520..2206be5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -546,8 +546,7 @@
                     * (1.0f - mKeyguardHeadsUpShowingAmount);
         }
 
-        if (mSystemEventAnimator.isAnimationRunning()
-                && !mNotificationMediaManager.isLockscreenWallpaperOnNotificationShade()) {
+        if (mSystemEventAnimator.isAnimationRunning()) {
             newAlpha = Math.min(newAlpha, mSystemEventAnimatorAlpha);
         } else {
             mView.setTranslationX(0);
@@ -704,21 +703,11 @@
 
     private StatusBarSystemEventDefaultAnimator getSystemEventAnimator(boolean isAnimationRunning) {
         return new StatusBarSystemEventDefaultAnimator(getResources(), (alpha) -> {
-            // TODO(b/273443374): remove if-else condition
-            if (!mNotificationMediaManager.isLockscreenWallpaperOnNotificationShade()) {
-                mSystemEventAnimatorAlpha = alpha;
-            } else {
-                mSystemEventAnimatorAlpha = 1f;
-            }
+            mSystemEventAnimatorAlpha = alpha;
             updateViewState();
             return Unit.INSTANCE;
         }, (translationX) -> {
-            // TODO(b/273443374): remove if-else condition
-            if (!mNotificationMediaManager.isLockscreenWallpaperOnNotificationShade()) {
-                mView.setTranslationX(translationX);
-            } else {
-                mView.setTranslationX(0);
-            }
+            mView.setTranslationX(translationX);
             return Unit.INSTANCE;
         }, isAnimationRunning);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
deleted file mode 100644
index 00fd9fb..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ /dev/null
@@ -1,434 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.phone;
-
-import android.annotation.Nullable;
-import android.app.IWallpaperManager;
-import android.app.IWallpaperManagerCallback;
-import android.app.WallpaperColors;
-import android.app.WallpaperManager;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Rect;
-import android.graphics.Xfermode;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.DrawableWrapper;
-import android.os.AsyncTask;
-import android.os.Handler;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.CoreStartable;
-import com.android.systemui.Dumpable;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.NotificationMediaManager;
-import com.android.systemui.user.data.model.SelectedUserModel;
-import com.android.systemui.user.data.model.SelectionStatus;
-import com.android.systemui.user.data.repository.UserRepository;
-import com.android.systemui.util.kotlin.JavaAdapter;
-
-import libcore.io.IoUtils;
-
-import java.io.PrintWriter;
-import java.util.Objects;
-
-import javax.inject.Inject;
-
-/**
- * Manages the lockscreen wallpaper.
- */
-@SysUISingleton
-public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implements Runnable,
-        Dumpable, CoreStartable {
-
-    private static final String TAG = "LockscreenWallpaper";
-
-    // TODO(b/253507223): temporary; remove this
-    private static final String DISABLED_ERROR_MESSAGE = "Methods from LockscreenWallpaper.java "
-            + "should not be called in this version. The lock screen wallpaper should be "
-            + "managed by the WallpaperManagerService and not by this class.";
-
-    private final NotificationMediaManager mMediaManager;
-    private final WallpaperManager mWallpaperManager;
-    private final KeyguardUpdateMonitor mUpdateMonitor;
-    private final Handler mH;
-    private final JavaAdapter mJavaAdapter;
-    private final UserRepository mUserRepository;
-
-    private boolean mCached;
-    private Bitmap mCache;
-    private int mCurrentUserId;
-    // The user selected in the UI, or null if no user is selected or UI doesn't support selecting
-    // users.
-    private UserHandle mSelectedUser;
-    private AsyncTask<Void, Void, LoaderResult> mLoader;
-
-    @Inject
-    public LockscreenWallpaper(WallpaperManager wallpaperManager,
-            @Nullable IWallpaperManager iWallpaperManager,
-            KeyguardUpdateMonitor keyguardUpdateMonitor,
-            DumpManager dumpManager,
-            NotificationMediaManager mediaManager,
-            @Main Handler mainHandler,
-            JavaAdapter javaAdapter,
-            UserRepository userRepository,
-            UserTracker userTracker) {
-        dumpManager.registerDumpable(getClass().getSimpleName(), this);
-        mWallpaperManager = wallpaperManager;
-        mCurrentUserId = userTracker.getUserId();
-        mUpdateMonitor = keyguardUpdateMonitor;
-        mMediaManager = mediaManager;
-        mH = mainHandler;
-        mJavaAdapter = javaAdapter;
-        mUserRepository = userRepository;
-
-        if (iWallpaperManager != null && !mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
-            // Service is disabled on some devices like Automotive
-            try {
-                iWallpaperManager.setLockWallpaperCallback(this);
-            } catch (RemoteException e) {
-                Log.e(TAG, "System dead?" + e);
-            }
-        }
-    }
-
-    @Override
-    public void start() {
-        if (!isLockscreenLiveWallpaperEnabled()) {
-            mJavaAdapter.alwaysCollectFlow(
-                    mUserRepository.getSelectedUser(), this::setSelectedUser);
-        }
-    }
-
-    public Bitmap getBitmap() {
-        assertLockscreenLiveWallpaperNotEnabled();
-
-        if (mCached) {
-            return mCache;
-        }
-        if (!mWallpaperManager.isWallpaperSupported()) {
-            mCached = true;
-            mCache = null;
-            return null;
-        }
-
-        LoaderResult result = loadBitmap(mCurrentUserId, mSelectedUser);
-        if (result.success) {
-            mCached = true;
-            mCache = result.bitmap;
-        }
-        return mCache;
-    }
-
-    public LoaderResult loadBitmap(int currentUserId, UserHandle selectedUser) {
-        // May be called on any thread - only use thread safe operations.
-
-        assertLockscreenLiveWallpaperNotEnabled();
-
-
-        if (!mWallpaperManager.isWallpaperSupported()) {
-            // When wallpaper is not supported, show the system wallpaper
-            return LoaderResult.success(null);
-        }
-
-        // Prefer the selected user (when specified) over the current user for the FLAG_SET_LOCK
-        // wallpaper.
-        final int lockWallpaperUserId =
-                selectedUser != null ? selectedUser.getIdentifier() : currentUserId;
-        ParcelFileDescriptor fd = mWallpaperManager.getWallpaperFile(
-                WallpaperManager.FLAG_LOCK, lockWallpaperUserId);
-
-        if (fd != null) {
-            try {
-                BitmapFactory.Options options = new BitmapFactory.Options();
-                options.inPreferredConfig = Bitmap.Config.HARDWARE;
-                return LoaderResult.success(BitmapFactory.decodeFileDescriptor(
-                        fd.getFileDescriptor(), null, options));
-            } catch (OutOfMemoryError e) {
-                Log.w(TAG, "Can't decode file", e);
-                return LoaderResult.fail();
-            } finally {
-                IoUtils.closeQuietly(fd);
-            }
-        } else {
-            if (selectedUser != null) {
-                // Show the selected user's static wallpaper.
-                return LoaderResult.success(mWallpaperManager.getBitmapAsUser(
-                        selectedUser.getIdentifier(), true /* hardware */));
-
-            } else {
-                // When there is no selected user, show the system wallpaper
-                return LoaderResult.success(null);
-            }
-        }
-    }
-
-    private void setSelectedUser(SelectedUserModel selectedUserModel) {
-        assertLockscreenLiveWallpaperNotEnabled();
-
-        if (selectedUserModel.getSelectionStatus().equals(SelectionStatus.SELECTION_IN_PROGRESS)) {
-            // Wait until the selection has finished before updating.
-            return;
-        }
-
-        int user = selectedUserModel.getUserInfo().id;
-        if (user != mCurrentUserId) {
-            if (mSelectedUser == null || user != mSelectedUser.getIdentifier()) {
-                mCached = false;
-            }
-            mCurrentUserId = user;
-        }
-    }
-
-    public void setSelectedUser(UserHandle selectedUser) {
-        assertLockscreenLiveWallpaperNotEnabled();
-
-        if (Objects.equals(selectedUser, mSelectedUser)) {
-            return;
-        }
-        mSelectedUser = selectedUser;
-        postUpdateWallpaper();
-    }
-
-    @Override
-    public void onWallpaperChanged() {
-        assertLockscreenLiveWallpaperNotEnabled();
-        // Called on Binder thread.
-        postUpdateWallpaper();
-    }
-
-    @Override
-    public void onWallpaperColorsChanged(WallpaperColors colors, int which, int userId) {
-        assertLockscreenLiveWallpaperNotEnabled();
-    }
-
-    private void postUpdateWallpaper() {
-        assertLockscreenLiveWallpaperNotEnabled();
-        if (mH == null) {
-            Log.wtfStack(TAG, "Trying to use LockscreenWallpaper before initialization.");
-            return;
-        }
-        mH.removeCallbacks(this);
-        mH.post(this);
-    }
-    @Override
-    public void run() {
-        // Called in response to onWallpaperChanged on the main thread.
-
-        assertLockscreenLiveWallpaperNotEnabled();
-
-        if (mLoader != null) {
-            mLoader.cancel(false /* interrupt */);
-        }
-
-        final int currentUser = mCurrentUserId;
-        final UserHandle selectedUser = mSelectedUser;
-        mLoader = new AsyncTask<Void, Void, LoaderResult>() {
-            @Override
-            protected LoaderResult doInBackground(Void... params) {
-                return loadBitmap(currentUser, selectedUser);
-            }
-
-            @Override
-            protected void onPostExecute(LoaderResult result) {
-                super.onPostExecute(result);
-                if (isCancelled()) {
-                    return;
-                }
-                if (result.success) {
-                    mCached = true;
-                    mCache = result.bitmap;
-                    mMediaManager.updateMediaMetaData(true /* metaDataChanged */);
-                }
-                mLoader = null;
-            }
-        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
-    }
-
-    // TODO(b/273443374): remove
-    public boolean isLockscreenLiveWallpaperEnabled() {
-        return mWallpaperManager.isLockscreenLiveWallpaperEnabled();
-    }
-
-    @Override
-    public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
-        pw.println(getClass().getSimpleName() + ":");
-        IndentingPrintWriter iPw = new IndentingPrintWriter(pw, "  ").increaseIndent();
-        iPw.println("mCached=" + mCached);
-        iPw.println("mCache=" + mCache);
-        iPw.println("mCurrentUserId=" + mCurrentUserId);
-        iPw.println("mSelectedUser=" + mSelectedUser);
-    }
-
-    private static class LoaderResult {
-        public final boolean success;
-        public final Bitmap bitmap;
-
-        LoaderResult(boolean success, Bitmap bitmap) {
-            this.success = success;
-            this.bitmap = bitmap;
-        }
-
-        static LoaderResult success(Bitmap b) {
-            return new LoaderResult(true, b);
-        }
-
-        static LoaderResult fail() {
-            return new LoaderResult(false, null);
-        }
-    }
-
-    /**
-     * Drawable that aligns left horizontally and center vertically (like ImageWallpaper).
-     *
-     * <p>Aligns to the center when showing on the smaller internal display of a multi display
-     * device.
-     */
-    public static class WallpaperDrawable extends DrawableWrapper {
-
-        private final ConstantState mState;
-        private final Rect mTmpRect = new Rect();
-        private boolean mIsOnSmallerInternalDisplays;
-
-        public WallpaperDrawable(Resources r, Bitmap b, boolean isOnSmallerInternalDisplays) {
-            this(r, new ConstantState(b), isOnSmallerInternalDisplays);
-        }
-
-        private WallpaperDrawable(Resources r, ConstantState state,
-                boolean isOnSmallerInternalDisplays) {
-            super(new BitmapDrawable(r, state.mBackground));
-            mState = state;
-            mIsOnSmallerInternalDisplays = isOnSmallerInternalDisplays;
-        }
-
-        @Override
-        public void setXfermode(@Nullable Xfermode mode) {
-            // DrawableWrapper does not call this for us.
-            getDrawable().setXfermode(mode);
-        }
-
-        @Override
-        public int getIntrinsicWidth() {
-            return -1;
-        }
-
-        @Override
-        public int getIntrinsicHeight() {
-            return -1;
-        }
-
-        @Override
-        protected void onBoundsChange(Rect bounds) {
-            int vwidth = getBounds().width();
-            int vheight = getBounds().height();
-            int dwidth = mState.mBackground.getWidth();
-            int dheight = mState.mBackground.getHeight();
-            float scale;
-            float dx = 0, dy = 0;
-
-            if (dwidth * vheight > vwidth * dheight) {
-                scale = (float) vheight / (float) dheight;
-            } else {
-                scale = (float) vwidth / (float) dwidth;
-            }
-
-            if (scale <= 1f) {
-                scale = 1f;
-            }
-            dy = (vheight - dheight * scale) * 0.5f;
-
-            int offsetX = 0;
-            // Offset to show the center area of the wallpaper on a smaller display for multi
-            // display device
-            if (mIsOnSmallerInternalDisplays) {
-                offsetX = bounds.centerX() - (Math.round(dwidth * scale) / 2);
-            }
-
-            mTmpRect.set(
-                    bounds.left + offsetX,
-                    bounds.top + Math.round(dy),
-                    bounds.left + Math.round(dwidth * scale) + offsetX,
-                    bounds.top + Math.round(dheight * scale + dy));
-
-            super.onBoundsChange(mTmpRect);
-        }
-
-        @Override
-        public ConstantState getConstantState() {
-            return mState;
-        }
-
-        /**
-         * Update bounds when the hosting display or the display size has changed.
-         *
-         * @param isOnSmallerInternalDisplays true if the drawable is on one of the internal
-         *                                    displays with the smaller area.
-         */
-        public void onDisplayUpdated(boolean isOnSmallerInternalDisplays) {
-            mIsOnSmallerInternalDisplays = isOnSmallerInternalDisplays;
-            onBoundsChange(getBounds());
-        }
-
-        static class ConstantState extends Drawable.ConstantState {
-
-            private final Bitmap mBackground;
-
-            ConstantState(Bitmap background) {
-                mBackground = background;
-            }
-
-            @Override
-            public Drawable newDrawable() {
-                return newDrawable(null);
-            }
-
-            @Override
-            public Drawable newDrawable(@Nullable Resources res) {
-                return new WallpaperDrawable(res, this, /* isOnSmallerInternalDisplays= */ false);
-            }
-
-            @Override
-            public int getChangingConfigurations() {
-                // DrawableWrapper already handles this for us.
-                return 0;
-            }
-        }
-    }
-
-    /**
-     * Feature b/253507223 will adapt the logic to always use the
-     * WallpaperManagerService to render the lock screen wallpaper.
-     * Methods of this class should not be called at all if the project flag is enabled.
-     * TODO(b/253507223) temporary assertion; remove this
-     */
-    private void assertLockscreenLiveWallpaperNotEnabled() {
-        if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
-            throw new IllegalStateException(DISABLED_ERROR_MESSAGE);
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 54d81b8..5a8b636 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -300,7 +300,8 @@
         mIconController.setIconVisibility(mSlotCast, false);
 
         // connected display
-        mIconController.setIcon(mSlotConnectedDisplay, R.drawable.stat_sys_connected_display, null);
+        mIconController.setIcon(mSlotConnectedDisplay, R.drawable.stat_sys_connected_display,
+                mResources.getString(R.string.connected_display_icon_desc));
         mIconController.setIconVisibility(mSlotConnectedDisplay, false);
 
         // hotspot
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 5b55264..744d70e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER;
+import static com.android.systemui.keyguard.shared.model.KeyguardState.GONE;
+import static com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER;
 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
 import static java.lang.Float.isNaN;
@@ -51,7 +54,6 @@
 import com.android.systemui.CoreStartable;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
 import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
 import com.android.systemui.dagger.SysUISingleton;
@@ -62,7 +64,9 @@
 import com.android.systemui.keyguard.shared.model.ScrimAlpha;
 import com.android.systemui.keyguard.shared.model.TransitionState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
+import com.android.systemui.res.R;
 import com.android.systemui.scrim.ScrimView;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
@@ -273,6 +277,7 @@
     private CoroutineDispatcher mMainDispatcher;
     private boolean mIsBouncerToGoneTransitionRunning = false;
     private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
+    private AlternateBouncerToGoneTransitionViewModel mAlternateBouncerToGoneTransitionViewModel;
     private final Consumer<ScrimAlpha> mScrimAlphaConsumer =
             (ScrimAlpha alphas) -> {
                 mInFrontAlpha = alphas.getFrontAlpha();
@@ -285,7 +290,7 @@
                 mScrimBehind.setViewAlpha(mBehindAlpha);
             };
 
-    Consumer<TransitionStep> mPrimaryBouncerToGoneTransition;
+    Consumer<TransitionStep> mBouncerToGoneTransition;
 
     @Inject
     public ScrimController(
@@ -304,6 +309,7 @@
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
+            AlternateBouncerToGoneTransitionViewModel alternateBouncerToGoneTransitionViewModel,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
             WallpaperRepository wallpaperRepository,
             @Main CoroutineDispatcher mainDispatcher,
@@ -349,6 +355,7 @@
         });
         mColors = new GradientColors();
         mPrimaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel;
+        mAlternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel;
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
         mWallpaperRepository = wallpaperRepository;
         mMainDispatcher = mainDispatcher;
@@ -405,7 +412,7 @@
         // Directly control transition to UNLOCKED scrim state from PRIMARY_BOUNCER, and make sure
         // to report back that keyguard has faded away. This fixes cases where the scrim state was
         // rapidly switching on unlock, due to shifts in state in CentralSurfacesImpl
-        mPrimaryBouncerToGoneTransition =
+        mBouncerToGoneTransition =
                 (TransitionStep step) -> {
                     TransitionState state = step.getTransitionState();
 
@@ -425,10 +432,17 @@
                     }
                 };
 
-        collectFlow(behindScrim, mKeyguardTransitionInteractor.getPrimaryBouncerToGoneTransition(),
-                mPrimaryBouncerToGoneTransition, mMainDispatcher);
+        // PRIMARY_BOUNCER->GONE
+        collectFlow(behindScrim, mKeyguardTransitionInteractor.transition(PRIMARY_BOUNCER, GONE),
+                mBouncerToGoneTransition, mMainDispatcher);
         collectFlow(behindScrim, mPrimaryBouncerToGoneTransitionViewModel.getScrimAlpha(),
                 mScrimAlphaConsumer, mMainDispatcher);
+
+        // ALTERNATE_BOUNCER->GONE
+        collectFlow(behindScrim, mKeyguardTransitionInteractor.transition(ALTERNATE_BOUNCER, GONE),
+                mBouncerToGoneTransition, mMainDispatcher);
+        collectFlow(behindScrim, mAlternateBouncerToGoneTransitionViewModel.getScrimAlpha(),
+                mScrimAlphaConsumer, mMainDispatcher);
     }
 
     // TODO(b/270984686) recompute scrim height accurately, based on shade contents.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 400ac7b..267b563 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -92,6 +92,7 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.unfold.FoldAodAnimationController;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 import dagger.Lazy;
 
@@ -313,6 +314,7 @@
     private final KeyguardUpdateMonitor mKeyguardUpdateManager;
     private final LatencyTracker mLatencyTracker;
     private final KeyguardSecurityModel mKeyguardSecurityModel;
+    private final SelectedUserInteractor mSelectedUserInteractor;
     @Nullable private KeyguardBypassController mBypassController;
     @Nullable private OccludingAppBiometricUI mOccludingAppBiometricUI;
 
@@ -370,7 +372,8 @@
             KeyguardTransitionInteractor keyguardTransitionInteractor,
             @Main CoroutineDispatcher mainDispatcher,
             Lazy<WindowManagerLockscreenVisibilityInteractor> wmLockscreenVisibilityInteractor,
-            Lazy<KeyguardDismissActionInteractor> keyguardDismissActionInteractorLazy
+            Lazy<KeyguardDismissActionInteractor> keyguardDismissActionInteractorLazy,
+            SelectedUserInteractor selectedUserInteractor
     ) {
         mContext = context;
         mViewMediatorCallback = callback;
@@ -403,6 +406,7 @@
         mMainDispatcher = mainDispatcher;
         mWmLockscreenVisibilityInteractor = wmLockscreenVisibilityInteractor;
         mKeyguardDismissActionInteractor = keyguardDismissActionInteractorLazy;
+        mSelectedUserInteractor = selectedUserInteractor;
     }
 
     KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@@ -960,9 +964,6 @@
             SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
                     SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN);
         }
-        if (isShowing) {
-            mMediaManager.updateMediaMetaData(false);
-        }
         mNotificationShadeWindowController.setKeyguardOccluded(isOccluded);
 
         // setDozing(false) will call reset once we stop dozing. Also, if we're going away, there's
@@ -1142,7 +1143,8 @@
      */
     public boolean isSecure() {
         return mKeyguardSecurityModel.getSecurityMode(
-                KeyguardUpdateMonitor.getCurrentUser()) != KeyguardSecurityModel.SecurityMode.None;
+                mSelectedUserInteractor.getSelectedUserId())
+                != KeyguardSecurityModel.SecurityMode.None;
     }
 
     /**
@@ -1690,7 +1692,7 @@
      */
     public boolean needsFullscreenBouncer() {
         KeyguardSecurityModel.SecurityMode mode = mKeyguardSecurityModel.getSecurityMode(
-                KeyguardUpdateMonitor.getCurrentUser());
+                mSelectedUserInteractor.getSelectedUserId());
         return mode == KeyguardSecurityModel.SecurityMode.SimPin
                 || mode == KeyguardSecurityModel.SecurityMode.SimPuk;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 57a8e6f..a5cd062 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -205,7 +205,6 @@
         // End old BaseStatusBar.userSwitched
         mCommandQueue.animateCollapsePanels();
         mMediaManager.clearCurrentMediaNotification();
-        updateMediaMetaData(true, false);
     }
 
     @Override
@@ -220,11 +219,6 @@
     }
 
     @Override
-    public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
-        mMediaManager.updateMediaMetaData(metaDataChanged);
-    }
-
-    @Override
     public void onExpandClicked(NotificationEntry clickedEntry, View clickedView,
             boolean nowExpanded) {
         mHeadsUpManager.setExpanded(clickedEntry, nowExpanded);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
index 85fd2af..d7f3b07 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
@@ -17,42 +17,71 @@
 
 import android.app.Dialog
 import android.content.Context
+import android.content.res.Configuration
 import android.graphics.Color
 import android.graphics.drawable.ColorDrawable
 import android.os.Bundle
 import android.view.Gravity
-import android.view.WindowManager
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.view.WindowManager.LayoutParams.MATCH_PARENT
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
+import android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
+import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL
 import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
 
 /** A dialog shown as a bottom sheet. */
 open class SystemUIBottomSheetDialog(
     context: Context,
-    theme: Int = R.style.Theme_SystemUI_Dialog,
+    private val configurationController: ConfigurationController? = null,
+    theme: Int = R.style.Theme_SystemUI_Dialog
 ) : Dialog(context, theme) {
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-
-        window?.apply {
-            setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
-            addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
-
-            setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
-            setGravity(Gravity.BOTTOM)
-            val edgeToEdgeHorizontally =
-                context.resources.getBoolean(R.bool.config_edgeToEdgeBottomSheetDialog)
-            if (edgeToEdgeHorizontally) {
-                decorView.setPadding(0, 0, 0, 0)
-                setLayout(
-                    WindowManager.LayoutParams.MATCH_PARENT,
-                    WindowManager.LayoutParams.WRAP_CONTENT
-                )
-
-                val lp = attributes
-                lp.fitInsetsSides = 0
-                lp.horizontalMargin = 0f
-                attributes = lp
-            }
-        }
+        setupWindow()
+        setupEdgeToEdge()
         setCanceledOnTouchOutside(true)
     }
+
+    private fun setupWindow() {
+        window?.apply {
+            setType(TYPE_STATUS_BAR_SUB_PANEL)
+            addPrivateFlags(SYSTEM_FLAG_SHOW_FOR_ALL_USERS or PRIVATE_FLAG_NO_MOVE_ANIMATION)
+            setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+            setGravity(Gravity.BOTTOM)
+            decorView.setPadding(0, 0, 0, 0)
+            attributes =
+                attributes.apply {
+                    fitInsetsSides = 0
+                    horizontalMargin = 0f
+                }
+        }
+    }
+
+    private fun setupEdgeToEdge() {
+        val edgeToEdgeHorizontally =
+            context.resources.getBoolean(R.bool.config_edgeToEdgeBottomSheetDialog)
+        val width = if (edgeToEdgeHorizontally) MATCH_PARENT else WRAP_CONTENT
+        val height = WRAP_CONTENT
+        window?.setLayout(width, height)
+    }
+
+    override fun onStart() {
+        super.onStart()
+        configurationController?.addCallback(onConfigChanged)
+    }
+
+    override fun onStop() {
+        super.onStop()
+        configurationController?.removeCallback(onConfigChanged)
+    }
+
+    private val onConfigChanged =
+        object : ConfigurationListener {
+            override fun onConfigChanged(newConfig: Configuration?) {
+                super.onConfigChanged(newConfig)
+                setupEdgeToEdge()
+            }
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 9d627af..2558645 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -45,6 +45,7 @@
 import com.android.systemui.res.R;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Application;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.model.SysUiState;
@@ -54,8 +55,14 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import javax.inject.Inject;
+
 /**
- * Base class for dialogs that should appear over panels and keyguard.
+ * Class for dialogs that should appear over panels and keyguard.
+ *
+ * DO NOT SUBCLASS THIS. See {@link SystemUIDialog.Delegate} for an interface that enables
+ * customizing behavior via composition instead of inheritance. Clients should implement the
+ * Delegate class and then pass their implementation into the SystemUIDialog constructor.
  *
  * Optionally provide a {@link SystemUIDialogManager} to its constructor to send signals to
  * listeners on whether this dialog is showing.
@@ -72,6 +79,7 @@
 
     private final Context mContext;
     private final FeatureFlags mFeatureFlags;
+    private final Delegate mDelegate;
     @Nullable private final DismissReceiver mDismissReceiver;
     private final Handler mHandler = new Handler();
     private final SystemUIDialogManager mDialogManager;
@@ -101,18 +109,102 @@
                 Dependency.get(SystemUIDialogManager.class),
                 Dependency.get(SysUiState.class),
                 Dependency.get(BroadcastDispatcher.class),
-                Dependency.get(DialogLaunchAnimator.class));
+                Dependency.get(DialogLaunchAnimator.class),
+                new Delegate() {});
     }
 
-    public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock,
+    @Inject
+    public SystemUIDialog(
+            @Application Context context,
+            FeatureFlags featureFlags,
+            SystemUIDialogManager systemUIDialogManager,
+            SysUiState sysUiState,
+            BroadcastDispatcher broadcastDispatcher,
+            DialogLaunchAnimator dialogLaunchAnimator) {
+        this(context,
+                DEFAULT_THEME,
+                DEFAULT_DISMISS_ON_DEVICE_LOCK,
+                featureFlags,
+                systemUIDialogManager,
+                sysUiState,
+                broadcastDispatcher,
+                dialogLaunchAnimator,
+                new Delegate(){});
+    }
+
+    public static class Factory {
+        private final Context mContext;
+        private final FeatureFlags mFeatureFlags;
+        private final SystemUIDialogManager mSystemUIDialogManager;
+        private final SysUiState mSysUiState;
+        private final BroadcastDispatcher mBroadcastDispatcher;
+        private final DialogLaunchAnimator mDialogLaunchAnimator;
+
+        @Inject
+        public Factory(
+                @Application Context context,
+                FeatureFlags featureFlags,
+                SystemUIDialogManager systemUIDialogManager,
+                SysUiState sysUiState,
+                BroadcastDispatcher broadcastDispatcher,
+                DialogLaunchAnimator dialogLaunchAnimator) {
+            mContext = context;
+            mFeatureFlags = featureFlags;
+            mSystemUIDialogManager = systemUIDialogManager;
+            mSysUiState = sysUiState;
+            mBroadcastDispatcher = broadcastDispatcher;
+            mDialogLaunchAnimator = dialogLaunchAnimator;
+        }
+
+        public SystemUIDialog create(Delegate delegate) {
+            return new SystemUIDialog(
+                    mContext,
+                    DEFAULT_THEME,
+                    DEFAULT_DISMISS_ON_DEVICE_LOCK,
+                    mFeatureFlags,
+                    mSystemUIDialogManager,
+                    mSysUiState,
+                    mBroadcastDispatcher,
+                    mDialogLaunchAnimator,
+                    delegate);
+        }
+    }
+
+    public SystemUIDialog(
+            Context context,
+            int theme,
+            boolean dismissOnDeviceLock,
             FeatureFlags featureFlags,
             SystemUIDialogManager dialogManager,
             SysUiState sysUiState,
             BroadcastDispatcher broadcastDispatcher,
             DialogLaunchAnimator dialogLaunchAnimator) {
+        this(
+                context,
+                theme,
+                dismissOnDeviceLock,
+                featureFlags,
+                dialogManager,
+                sysUiState,
+                broadcastDispatcher,
+                dialogLaunchAnimator,
+                new Delegate(){});
+    }
+
+    public SystemUIDialog(
+            Context context,
+            int theme,
+            boolean dismissOnDeviceLock,
+            FeatureFlags featureFlags,
+            SystemUIDialogManager dialogManager,
+            SysUiState sysUiState,
+            BroadcastDispatcher broadcastDispatcher,
+            DialogLaunchAnimator dialogLaunchAnimator,
+            Delegate delegate) {
         super(context, theme);
         mContext = context;
         mFeatureFlags = featureFlags;
+        mDelegate = delegate;
 
         applyFlags(this);
         WindowManager.LayoutParams attrs = getWindow().getAttributes();
@@ -127,7 +219,9 @@
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
+        mDelegate.beforeCreate(this, savedInstanceState);
         super.onCreate(savedInstanceState);
+        mDelegate.onCreate(this, savedInstanceState);
 
         Configuration config = getContext().getResources().getConfiguration();
         mLastConfigurationWidthDp = config.screenWidthDp;
@@ -172,6 +266,8 @@
 
             updateWindowSize();
         }
+
+        mDelegate.onConfigurationChanged(this, configuration);
     }
 
     /**
@@ -212,7 +308,9 @@
      * Called when {@link #onStart} is called. Subclasses wishing to override {@link #onStart()}
      * should override this method instead.
      */
-    protected void start() {}
+    protected void start() {
+        mDelegate.start(this);
+    }
 
     @Override
     protected final void onStop() {
@@ -234,7 +332,15 @@
      * Called when {@link #onStop} is called. Subclasses wishing to override {@link #onStop()}
      * should override this method instead.
      */
-    protected void stop() {}
+    protected void stop() {
+        mDelegate.stop(this);
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasFocus) {
+        super.onWindowFocusChanged(hasFocus);
+        mDelegate.onWindowFocusChanged(this, hasFocus);
+    }
 
     public void setShowForAllUsers(boolean show) {
         setShowForAllUsers(this, show);
@@ -353,7 +459,6 @@
         registerDismissListener(dialog, null);
     }
 
-
     /**
      * Registers a listener that dismisses the given dialog when it receives
      * the screen off / close system dialogs broadcast.
@@ -480,4 +585,42 @@
         }
     }
 
+    /**
+     * A delegate class that should be implemented in place of sublcassing {@link SystemUIDialog}.
+     *
+     * Implement this interface and then pass an instance of your implementation to
+     * {@link SystemUIDialog.Factory#create(Delegate)}.
+     */
+    public interface Delegate {
+        /**
+         * Called before {@link AlertDialog#onCreate} is called.
+         */
+        default void beforeCreate(SystemUIDialog dialog, Bundle savedInstanceState) {}
+
+        /**
+         * Called after {@link AlertDialog#onCreate} is called.
+         */
+        default void onCreate(SystemUIDialog dialog, Bundle savedInstanceState) {}
+
+        /**
+         * Called after {@link AlertDialog#onStart} is called.
+         */
+        default void start(SystemUIDialog dialog) {}
+
+        /**
+         * Called after {@link AlertDialog#onStop} is called.
+         */
+        default void stop(SystemUIDialog dialog) {}
+
+        /**
+         * Called after {@link AlertDialog#onWindowFocusChanged(boolean)} is called.
+         */
+        default void onWindowFocusChanged(SystemUIDialog dialog, boolean hasFocus) {}
+
+        /**
+         * Called as part of
+         * {@link ViewRootImpl.ConfigChangedCallback#onConfigurationChanged(Configuration)}.
+         */
+        default void onConfigurationChanged(SystemUIDialog dialog, Configuration configuration) {}
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
index e9e52a2..1670dd3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
@@ -104,7 +104,7 @@
                 val callback =
                     object : WifiPickerTracker.WifiPickerTrackerCallback {
                         override fun onWifiEntriesChanged() {
-                            val connectedEntry = wifiPickerTracker?.connectedWifiEntry
+                            val connectedEntry = wifiPickerTracker.mergedOrPrimaryConnection
                             logOnWifiEntriesChanged(connectedEntry)
 
                             val secondaryNetworks =
@@ -217,6 +217,21 @@
             .stateIn(scope, SharingStarted.Eagerly, emptyList())
 
     /**
+     * [WifiPickerTracker.getConnectedWifiEntry] stores a [MergedCarrierEntry] separately from the
+     * [WifiEntry] for the primary connection. Therefore, we have to prefer the carrier-merged entry
+     * if it exists, falling back on the connected entry if null
+     */
+    private val WifiPickerTracker?.mergedOrPrimaryConnection: WifiEntry?
+        get() {
+            val mergedEntry: MergedCarrierEntry? = this?.mergedCarrierEntry
+            return if (mergedEntry != null && mergedEntry.isDefaultNetwork) {
+                mergedEntry
+            } else {
+                this?.connectedWifiEntry
+            }
+        }
+
+    /**
      * Converts WifiTrackerLib's [WifiEntry] into our internal model only if the entry is the
      * primary network. Returns an inactive network if it's not primary.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 1c88289..c624518 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -37,11 +37,12 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
 import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.res.R;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 import dagger.Lazy;
 
@@ -64,6 +65,7 @@
     private final Context mContext;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final LockPatternUtils mLockPatternUtils;
+    private final SelectedUserInteractor mUserInteractor;
     private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
             new UpdateMonitorCallback();
     private final Lazy<KeyguardUnlockAnimationController> mUnlockAnimationControllerLazy;
@@ -120,11 +122,13 @@
             Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationController,
             KeyguardUpdateMonitorLogger logger,
             DumpManager dumpManager,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags,
+            SelectedUserInteractor userInteractor) {
         mContext = context;
         mLogger = logger;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mLockPatternUtils = lockPatternUtils;
+        mUserInteractor = userInteractor;
         mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
         mUnlockAnimationControllerLazy = keyguardUnlockAnimationController;
         mFeatureFlags = featureFlags;
@@ -250,7 +254,7 @@
     @VisibleForTesting
     void update(boolean updateAlways) {
         Trace.beginSection("KeyguardStateController#update");
-        int user = KeyguardUpdateMonitor.getCurrentUser();
+        int user = mUserInteractor.getSelectedUserId();
         boolean secure = mLockPatternUtils.isSecure(user);
         boolean canDismissLockScreen = !secure || mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)
                 || (Build.IS_DEBUGGABLE && DEBUG_AUTH_WITH_ADB && mDebugUnlocked);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
index f88339a..7829d6e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
@@ -27,7 +27,7 @@
 import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower
 import com.android.systemui.user.data.source.UserRecord
 import com.android.systemui.user.domain.interactor.GuestUserInteractor
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
 import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
 import dagger.Lazy
 import java.io.PrintWriter
@@ -41,7 +41,7 @@
 @Inject
 constructor(
     @Application private val applicationContext: Context,
-    private val userInteractorLazy: Lazy<UserInteractor>,
+    private val userSwitcherInteractorLazy: Lazy<UserSwitcherInteractor>,
     private val guestUserInteractorLazy: Lazy<GuestUserInteractor>,
     private val keyguardInteractorLazy: Lazy<KeyguardInteractor>,
     private val activityStarter: ActivityStarter,
@@ -53,26 +53,29 @@
         fun onUserSwitched()
     }
 
-    private val userInteractor: UserInteractor by lazy { userInteractorLazy.get() }
+    private val mUserSwitcherInteractor: UserSwitcherInteractor by lazy {
+        userSwitcherInteractorLazy.get()
+    }
     private val guestUserInteractor: GuestUserInteractor by lazy { guestUserInteractorLazy.get() }
     private val keyguardInteractor: KeyguardInteractor by lazy { keyguardInteractorLazy.get() }
 
-    private val callbackCompatMap = mutableMapOf<UserSwitchCallback, UserInteractor.UserCallback>()
+    private val callbackCompatMap =
+        mutableMapOf<UserSwitchCallback, UserSwitcherInteractor.UserCallback>()
 
     /** The current list of [UserRecord]. */
     val users: ArrayList<UserRecord>
-        get() = userInteractor.userRecords.value
+        get() = mUserSwitcherInteractor.userRecords.value
 
     /** Whether the user switcher experience should use the simple experience. */
     val isSimpleUserSwitcher: Boolean
-        get() = userInteractor.isSimpleUserSwitcher
+        get() = mUserSwitcherInteractor.isSimpleUserSwitcher
 
     val isUserSwitcherEnabled: Boolean
-        get() = userInteractor.isUserSwitcherEnabled
+        get() = mUserSwitcherInteractor.isUserSwitcherEnabled
 
     /** The [UserRecord] of the current user or `null` when none. */
     val currentUserRecord: UserRecord?
-        get() = userInteractor.selectedUserRecord.value
+        get() = mUserSwitcherInteractor.selectedUserRecord.value
 
     /** The name of the current user of the device or `null`, when none is selected. */
     val currentUserName: String?
@@ -81,8 +84,8 @@
                 LegacyUserUiHelper.getUserRecordName(
                     context = applicationContext,
                     record = it,
-                    isGuestUserAutoCreated = userInteractor.isGuestUserAutoCreated,
-                    isGuestUserResetting = userInteractor.isGuestUserResetting,
+                    isGuestUserAutoCreated = mUserSwitcherInteractor.isGuestUserAutoCreated,
+                    isGuestUserResetting = mUserSwitcherInteractor.isGuestUserResetting,
                 )
             }
 
@@ -98,21 +101,21 @@
      * @param dialogShower An optional [DialogShower] in case we need to show dialogs.
      */
     fun onUserSelected(userId: Int, dialogShower: DialogShower?) {
-        userInteractor.selectUser(userId, dialogShower)
+        mUserSwitcherInteractor.selectUser(userId, dialogShower)
     }
 
     /** Whether the guest user is configured to always be present on the device. */
     val isGuestUserAutoCreated: Boolean
-        get() = userInteractor.isGuestUserAutoCreated
+        get() = mUserSwitcherInteractor.isGuestUserAutoCreated
 
     /** Whether the guest user is currently being reset. */
     val isGuestUserResetting: Boolean
-        get() = userInteractor.isGuestUserResetting
+        get() = mUserSwitcherInteractor.isGuestUserResetting
 
     /** Registers an adapter to notify when the users change. */
     fun addAdapter(adapter: WeakReference<BaseUserSwitcherAdapter>) {
-        userInteractor.addCallback(
-            object : UserInteractor.UserCallback {
+        mUserSwitcherInteractor.addCallback(
+            object : UserSwitcherInteractor.UserCallback {
                 override fun isEvictable(): Boolean {
                     return adapter.get() == null
                 }
@@ -129,7 +132,7 @@
         record: UserRecord,
         dialogShower: DialogShower?,
     ) {
-        userInteractor.onRecordSelected(record, dialogShower)
+        mUserSwitcherInteractor.onRecordSelected(record, dialogShower)
     }
 
     /**
@@ -152,7 +155,7 @@
      *   `UserHandle.USER_NULL`, then switch immediately to the newly created guest user.
      */
     fun removeGuestUser(guestUserId: Int, targetUserId: Int) {
-        userInteractor.removeGuestUser(
+        mUserSwitcherInteractor.removeGuestUser(
             guestUserId = guestUserId,
             targetUserId = targetUserId,
         )
@@ -168,7 +171,7 @@
      *   only if its ephemeral, else keep guest
      */
     fun exitGuestUser(guestUserId: Int, targetUserId: Int, forceRemoveGuestOnExit: Boolean) {
-        userInteractor.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit)
+        mUserSwitcherInteractor.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit)
     }
 
     /**
@@ -194,31 +197,31 @@
      * The pictures are only loaded if they have not been loaded yet.
      */
     fun refreshUsers() {
-        userInteractor.refreshUsers()
+        mUserSwitcherInteractor.refreshUsers()
     }
 
     /** Adds a subscriber to when user switches. */
     fun addUserSwitchCallback(callback: UserSwitchCallback) {
         val interactorCallback =
-            object : UserInteractor.UserCallback {
+            object : UserSwitcherInteractor.UserCallback {
                 override fun onUserStateChanged() {
                     callback.onUserSwitched()
                 }
             }
         callbackCompatMap[callback] = interactorCallback
-        userInteractor.addCallback(interactorCallback)
+        mUserSwitcherInteractor.addCallback(interactorCallback)
     }
 
     /** Removes a previously-added subscriber. */
     fun removeUserSwitchCallback(callback: UserSwitchCallback) {
         val interactorCallback = callbackCompatMap.remove(callback)
         if (interactorCallback != null) {
-            userInteractor.removeCallback(interactorCallback)
+            mUserSwitcherInteractor.removeCallback(interactorCallback)
         }
     }
 
     fun dump(pw: PrintWriter, args: Array<out String>) {
-        userInteractor.dump(pw)
+        mUserSwitcherInteractor.dump(pw)
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt
index 9269df3..8c66c2f 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt
@@ -19,6 +19,7 @@
 import android.content.ContentResolver
 import android.content.Context
 import android.hardware.devicestate.DeviceStateManager
+import android.os.Trace
 import android.util.Log
 import com.android.internal.util.LatencyTracker
 import com.android.systemui.dagger.SysUISingleton
@@ -57,6 +58,7 @@
     private var folded: Boolean? = null
     private var isTransitionEnabled: Boolean? = null
     private val foldStateListener = FoldStateListener(context)
+    private var unfoldInProgress = false
     private val isFoldable: Boolean
         get() =
             context.resources
@@ -95,7 +97,7 @@
         // the unfold animation (e.g. it could be disabled because of battery saver).
         // When animation is enabled finishing of the tracking will be done in onTransitionStarted.
         if (folded == false && isTransitionEnabled == false) {
-            latencyTracker.onActionEnd(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD)
+            onUnfoldEnded()
 
             if (DEBUG) {
                 Log.d(TAG, "onScreenTurnedOn: ending ACTION_SWITCH_DISPLAY_UNFOLD")
@@ -116,7 +118,7 @@
         }
 
         if (folded == false && isTransitionEnabled == true) {
-            latencyTracker.onActionEnd(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD)
+            onUnfoldEnded()
 
             if (DEBUG) {
                 Log.d(TAG, "onTransitionStarted: ending ACTION_SWITCH_DISPLAY_UNFOLD")
@@ -124,6 +126,22 @@
         }
     }
 
+    private fun onUnfoldStarted() {
+        if (unfoldInProgress) return
+        unfoldInProgress = true
+        // As LatencyTracker might be disabled, let's also log a parallel slice to the trace to be
+        // able to debug all cases.
+        latencyTracker.onActionStart(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD)
+        Trace.asyncTraceBegin(Trace.TRACE_TAG_APP, UNFOLD_IN_PROGRESS_TRACE_NAME, /* cookie= */ 0)
+    }
+
+    private fun onUnfoldEnded() {
+        if (!unfoldInProgress) return
+        unfoldInProgress = false
+        latencyTracker.onActionEnd(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD)
+        Trace.endAsyncSection(UNFOLD_IN_PROGRESS_TRACE_NAME, 0)
+    }
+
     private fun onFoldEvent(folded: Boolean) {
         val oldFolded = this.folded
 
@@ -139,7 +157,7 @@
             // unfolding the device.
             if (oldFolded != null && !folded) {
                 // Unfolding started
-                latencyTracker.onActionStart(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD)
+                onUnfoldStarted()
                 isTransitionEnabled =
                     transitionProgressProvider.isPresent && contentResolver.areAnimationsEnabled()
 
@@ -159,4 +177,5 @@
 }
 
 private const val TAG = "UnfoldLatencyTracker"
+private const val UNFOLD_IN_PROGRESS_TRACE_NAME = "Switch displays during unfold"
 private val DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE)
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt
new file mode 100644
index 0000000..ed960f3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.unfold
+
+import android.content.Context
+import android.os.Trace
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.unfold.system.DeviceStateRepository
+import com.android.systemui.unfold.updates.FoldStateRepository
+import com.android.systemui.util.TraceStateLogger
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Logs several unfold related details in a trace. Mainly used for debugging and investigate
+ * droidfooders traces.
+ */
+@SysUISingleton
+class UnfoldTraceLogger
+@Inject
+constructor(
+    private val context: Context,
+    private val foldStateRepository: FoldStateRepository,
+    @Application private val applicationScope: CoroutineScope,
+    private val deviceStateRepository: DeviceStateRepository
+) : CoreStartable {
+    private val isFoldable: Boolean
+        get() =
+            context.resources
+                .getIntArray(com.android.internal.R.array.config_foldedDeviceStates)
+                .isNotEmpty()
+
+    override fun start() {
+        if (!isFoldable) return
+
+        applicationScope.launch {
+            val foldUpdateLogger = TraceStateLogger("FoldUpdate")
+            foldStateRepository.foldUpdate.collect { foldUpdateLogger.log(it.name) }
+        }
+
+        applicationScope.launch {
+            foldStateRepository.hingeAngle.collect {
+                Trace.traceCounter(Trace.TRACE_TAG_APP, "hingeAngle", it.toInt())
+            }
+        }
+        applicationScope.launch {
+            val foldedStateLogger = TraceStateLogger("FoldedState")
+            deviceStateRepository.isFolded.collect { isFolded ->
+                foldedStateLogger.log(
+                    if (isFolded) {
+                        "folded"
+                    } else {
+                        "unfolded"
+                    }
+                )
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
index ed3eacd..71314f1 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.hardware.devicestate.DeviceStateManager
 import android.os.SystemProperties
+import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.LifecycleScreenStatusProvider
@@ -34,16 +35,26 @@
 import com.android.systemui.unfold.util.UnfoldTransitionATracePrefix
 import com.android.systemui.util.time.SystemClockImpl
 import com.android.wm.shell.unfold.ShellUnfoldProgressProvider
+import dagger.Binds
 import dagger.Lazy
 import dagger.Module
 import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
 import java.util.Optional
 import java.util.concurrent.Executor
 import javax.inject.Named
 import javax.inject.Provider
 import javax.inject.Singleton
 
-@Module(includes = [UnfoldSharedModule::class, SystemUnfoldSharedModule::class])
+@Module(
+    includes =
+        [
+            UnfoldSharedModule::class,
+            SystemUnfoldSharedModule::class,
+            UnfoldTransitionModule.Bindings::class
+        ]
+)
 class UnfoldTransitionModule {
 
     @Provides @UnfoldTransitionATracePrefix fun tracingTagPrefix() = "systemui"
@@ -136,13 +147,22 @@
                 null
             }
 
-        return resultingProvider?.get()?.orElse(null)?.let {
-            unfoldProgressProvider -> UnfoldProgressProvider(unfoldProgressProvider, foldProvider)
-        } ?: ShellUnfoldProgressProvider.NO_PROVIDER
+        return resultingProvider?.get()?.orElse(null)?.let { unfoldProgressProvider ->
+            UnfoldProgressProvider(unfoldProgressProvider, foldProvider)
+        }
+            ?: ShellUnfoldProgressProvider.NO_PROVIDER
     }
 
     @Provides
     fun screenStatusProvider(impl: LifecycleScreenStatusProvider): ScreenStatusProvider = impl
+
+    @Module
+    interface Bindings {
+        @Binds
+        @IntoMap
+        @ClassKey(UnfoldTraceLogger::class)
+        fun bindUnfoldTraceLogger(impl: UnfoldTraceLogger): CoreStartable
+    }
 }
 
 const val UNFOLD_STATUS_BAR = "unfold_status_bar"
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt
index dc7fadd..12387893 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt
@@ -21,7 +21,6 @@
 import android.os.Handler
 import android.os.UserManager
 import android.provider.Settings.Global.USER_SWITCHER_ENABLED
-import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
@@ -30,7 +29,6 @@
 import com.android.systemui.qs.SettingObserver
 import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel
 import com.android.systemui.res.R
-import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.UserInfoController
 import com.android.systemui.statusbar.policy.UserSwitcherController
 import com.android.systemui.util.settings.GlobalSettings
@@ -61,10 +59,10 @@
     @Background private val bgHandler: Handler,
     @Background private val bgDispatcher: CoroutineDispatcher,
     private val userManager: UserManager,
-    private val userTracker: UserTracker,
     private val userSwitcherController: UserSwitcherController,
     private val userInfoController: UserInfoController,
     private val globalSetting: GlobalSettings,
+    private val userRepository: UserRepository,
 ) : UserSwitcherRepository {
     private val showUserSwitcherForSingleUser =
         context.resources.getBoolean(R.bool.qs_show_user_switcher_for_single_user)
@@ -80,7 +78,7 @@
                     globalSetting,
                     bgHandler,
                     USER_SWITCHER_ENABLED,
-                    userTracker.userId,
+                    userRepository.getSelectedUserInfo().id,
                 ) {
                 override fun handleValueChanged(value: Int, observedChange: Boolean) {
                     if (observedChange) {
@@ -147,7 +145,7 @@
 
     private suspend fun isGuestUser(): Boolean {
         return withContext(bgDispatcher) {
-            userManager.isGuestUser(KeyguardUpdateMonitor.getCurrentUser())
+            userManager.isGuestUser(userRepository.getSelectedUserInfo().id)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt
new file mode 100644
index 0000000..0e693d0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt
@@ -0,0 +1,38 @@
+package com.android.systemui.user.domain.interactor
+
+import android.annotation.UserIdInt
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER
+import com.android.systemui.user.data.repository.UserRepository
+import javax.inject.Inject
+
+/** Encapsulates business logic to interact the selected user */
+@SysUISingleton
+class SelectedUserInteractor
+@Inject
+constructor(
+    private val repository: UserRepository,
+    private val flags: FeatureFlagsClassic,
+) {
+
+    /**
+     * Returns the ID of the currently-selected user.
+     *
+     * @param bypassFlag this will ignore the feature flag and get the data from the repository
+     *   instead. This is used for refactored methods that were previously pointing to `userTracker`
+     *   and therefore should not be routed back to KeyguardUpdateMonitor when flag is disabled.
+     *   KeyguardUpdateMonitor.getCurrentUser() is deprecated and will be removed soon (together
+     *   with this flag).
+     */
+    @UserIdInt
+    @JvmOverloads
+    fun getSelectedUserId(bypassFlag: Boolean = false): Int {
+        if (bypassFlag || flags.isEnabled(REFACTOR_GETCURRENTUSER)) {
+            return repository.getSelectedUserInfo().id
+        } else {
+            return KeyguardUpdateMonitor.getCurrentUser()
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
index dbc3bf3..e0d205f 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -83,9 +83,9 @@
 import kotlinx.coroutines.sync.withLock
 import kotlinx.coroutines.withContext
 
-/** Encapsulates business logic to interact with user data and systems. */
+/** Encapsulates business logic to for the user switcher. */
 @SysUISingleton
-class UserInteractor
+class UserSwitcherInteractor
 @Inject
 constructor(
     @Application private val applicationContext: Context,
@@ -383,10 +383,6 @@
         pw.println("isGuestUserAutoCreated=$isGuestUserAutoCreated")
     }
 
-    fun onDeviceBootCompleted() {
-        guestUserInteractor.onDeviceBootCompleted()
-    }
-
     /** Switches to the user or executes the action represented by the given record. */
     fun onRecordSelected(
         record: UserRecord,
@@ -535,12 +531,6 @@
         }
     }
 
-    /** Returns the ID of the currently-selected user. */
-    @UserIdInt
-    fun getSelectedUserId(): Int {
-        return repository.getSelectedUserInfo().id
-    }
-
     private fun showDialog(request: ShowDialogRequestModel) {
         _dialogShowRequests.value = request
     }
@@ -664,7 +654,6 @@
 
         // Connect to the new secondary user's service (purely to ensure that a persistent
         // SystemUI application is created for that user)
-
         if (userId != Process.myUserHandle().identifier) {
             applicationContext.startServiceAsUser(
                 intent,
@@ -826,6 +815,6 @@
     }
 
     companion object {
-        private const val TAG = "UserInteractor"
+        private const val TAG = "UserSwitcherInteractor"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
index 0930cb8..922dc05 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
@@ -33,7 +33,7 @@
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.qs.tiles.UserDetailView
 import com.android.systemui.user.UserSwitchFullscreenDialog
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
 import com.android.systemui.user.domain.model.ShowDialogRequestModel
 import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
 import dagger.Lazy
@@ -53,7 +53,7 @@
     private val falsingManager: Lazy<FalsingManager>,
     private val broadcastSender: Lazy<BroadcastSender>,
     private val dialogLaunchAnimator: Lazy<DialogLaunchAnimator>,
-    private val interactor: Lazy<UserInteractor>,
+    private val interactor: Lazy<UserSwitcherInteractor>,
     private val userDetailAdapterProvider: Provider<UserDetailView.Adapter>,
     private val eventLogger: Lazy<UiEventLogger>,
     private val activityStarter: Lazy<ActivityStarter>,
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
index 78edad7..2c425b19 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
@@ -17,12 +17,10 @@
 
 package com.android.systemui.user.ui.viewmodel
 
-import android.content.Context
 import android.graphics.drawable.Drawable
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Text
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
@@ -33,8 +31,7 @@
 class StatusBarUserChipViewModel
 @Inject
 constructor(
-    @Application private val context: Context,
-    interactor: UserInteractor,
+    interactor: UserSwitcherInteractor,
 ) {
     /** Whether the status bar chip ui should be available */
     val chipEnabled: Boolean = interactor.isStatusBarUserChipEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
index 20f0fa8c..4089889 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
@@ -20,9 +20,8 @@
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.common.ui.drawable.CircularDrawable
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.res.R
 import com.android.systemui.user.domain.interactor.GuestUserInteractor
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
 import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
 import com.android.systemui.user.shared.model.UserActionModel
 import com.android.systemui.user.shared.model.UserModel
@@ -38,17 +37,17 @@
 class UserSwitcherViewModel
 @Inject
 constructor(
-    private val userInteractor: UserInteractor,
+    private val userSwitcherInteractor: UserSwitcherInteractor,
     private val guestUserInteractor: GuestUserInteractor,
 ) {
 
     /** The currently selected user. */
     val selectedUser: Flow<UserViewModel> =
-        userInteractor.selectedUser.map { user -> toViewModel(user) }
+        userSwitcherInteractor.selectedUser.map { user -> toViewModel(user) }
 
     /** On-device users. */
     val users: Flow<List<UserViewModel>> =
-        userInteractor.users.map { models -> models.map { user -> toViewModel(user) } }
+        userSwitcherInteractor.users.map { models -> models.map { user -> toViewModel(user) } }
 
     /** The maximum number of columns that the user selection grid should use. */
     val maximumUserColumns: Flow<Int> = users.map { getMaxUserSwitcherItemColumns(it.size) }
@@ -61,7 +60,9 @@
     val isMenuVisible: Flow<Boolean> = _isMenuVisible
     /** The user action menu. */
     val menu: Flow<List<UserActionViewModel>> =
-        userInteractor.actions.map { actions -> actions.map { action -> toViewModel(action) } }
+        userSwitcherInteractor.actions.map { actions ->
+            actions.map { action -> toViewModel(action) }
+        }
 
     /** Whether the button to open the user action menu is visible. */
     val isOpenMenuButtonVisible: Flow<Boolean> = menu.map { it.isNotEmpty() }
@@ -175,7 +176,7 @@
                     isTablet = true,
                 ),
             onClicked = {
-                userInteractor.executeAction(action = model)
+                userSwitcherInteractor.executeAction(action = model)
                 // We don't finish because we want to show a dialog over the full-screen UI and
                 // that dialog can be dismissed in case the user changes their mind and decides not
                 // to add a user.
@@ -195,7 +196,7 @@
             null
         } else {
             {
-                userInteractor.selectUser(model.id)
+                userSwitcherInteractor.selectUser(model.id)
                 userSwitched.value = true
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
index 83ff789..b3834f5 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
@@ -269,3 +269,108 @@
     crossinline getValue: () -> T,
 ): StateFlow<T> =
     changedSignals.map { getValue() }.stateIn(this, SharingStarted.Eagerly, getValue())
+
+inline fun <T1, T2, T3, T4, T5, T6, R> combine(
+        flow: Flow<T1>,
+        flow2: Flow<T2>,
+        flow3: Flow<T3>,
+        flow4: Flow<T4>,
+        flow5: Flow<T5>,
+        flow6: Flow<T6>,
+        crossinline transform: suspend (T1, T2, T3, T4, T5, T6) -> R
+): Flow<R> {
+    return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6) {
+        args: Array<*> ->
+        @Suppress("UNCHECKED_CAST")
+        transform(
+                args[0] as T1,
+                args[1] as T2,
+                args[2] as T3,
+                args[3] as T4,
+                args[4] as T5,
+                args[5] as T6
+        )
+    }
+}
+
+inline fun <T1, T2, T3, T4, T5, T6, T7, R> combine(
+        flow: Flow<T1>,
+        flow2: Flow<T2>,
+        flow3: Flow<T3>,
+        flow4: Flow<T4>,
+        flow5: Flow<T5>,
+        flow6: Flow<T6>,
+        flow7: Flow<T7>,
+        crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7) -> R
+): Flow<R> {
+    return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6, flow7) {
+        args: Array<*> ->
+        @Suppress("UNCHECKED_CAST")
+        transform(
+                args[0] as T1,
+                args[1] as T2,
+                args[2] as T3,
+                args[3] as T4,
+                args[4] as T5,
+                args[5] as T6,
+                args[6] as T7
+        )
+    }
+}
+
+inline fun <T1, T2, T3, T4, T5, T6, T7, T8, R> combine(
+        flow: Flow<T1>,
+        flow2: Flow<T2>,
+        flow3: Flow<T3>,
+        flow4: Flow<T4>,
+        flow5: Flow<T5>,
+        flow6: Flow<T6>,
+        flow7: Flow<T7>,
+        flow8: Flow<T8>,
+        crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8) -> R
+): Flow<R> {
+    return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6, flow7, flow8) {
+        args: Array<*> ->
+        @Suppress("UNCHECKED_CAST")
+        transform(
+                args[0] as T1,
+                args[1] as T2,
+                args[2] as T3,
+                args[3] as T4,
+                args[4] as T5,
+                args[5] as T6,
+                args[6] as T7,
+                args[7] as T8
+        )
+    }
+}
+
+inline fun <T1, T2, T3, T4, T5, T6, T7, T8, T9, R> combine(
+        flow: Flow<T1>,
+        flow2: Flow<T2>,
+        flow3: Flow<T3>,
+        flow4: Flow<T4>,
+        flow5: Flow<T5>,
+        flow6: Flow<T6>,
+        flow7: Flow<T7>,
+        flow8: Flow<T8>,
+        flow9: Flow<T9>,
+        crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8, T9) -> R
+): Flow<R> {
+    return kotlinx.coroutines.flow.combine(
+        flow, flow2, flow3, flow4, flow5, flow6, flow7, flow8, flow9
+    ) { args: Array<*> ->
+        @Suppress("UNCHECKED_CAST")
+        transform(
+                args[0] as T1,
+                args[1] as T2,
+                args[2] as T3,
+                args[3] as T4,
+                args[4] as T5,
+                args[5] as T6,
+                args[6] as T7,
+                args[6] as T8,
+                args[6] as T9,
+        )
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
index 9b06a37..d566725 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
@@ -191,7 +191,7 @@
     }
 
     @Override
-    protected boolean initDataInjectionImpl(boolean enable) {
+    protected boolean initDataInjectionImpl(boolean enable, @DataInjectionMode int mode) {
         throw new UnsupportedOperationException("not implemented");
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/Events.java b/packages/SystemUI/src/com/android/systemui/volume/Events.java
index 2d1e622..50d1547 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/Events.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/Events.java
@@ -234,6 +234,10 @@
         VOLUME_DIALOG_SLIDER(150),
         @UiEvent(doc = "The audio stream was set to silent via slider")
         VOLUME_DIALOG_SLIDER_TO_ZERO(151),
+        @UiEvent(doc = "ODI captions was clicked")
+        VOLUME_DIALOG_ODI_CAPTIONS_CLICKED(1503),
+        @UiEvent(doc = "ODI captions tooltip dismiss was clicked")
+        VOLUME_DIALOG_ODI_CAPTIONS_TOOLTIP_CLICKED(1504),
         @UiEvent(doc = "The audio volume was adjusted to silent via key")
         VOLUME_KEY_TO_ZERO(152),
         @UiEvent(doc = "The audio volume was adjusted to non-silent via key")
@@ -362,6 +366,10 @@
             if (tag == EVENT_SETTINGS_CLICK) {
                 sLegacyLogger.action(MetricsEvent.ACTION_VOLUME_SETTINGS);
                 sUiEventLogger.log(VolumeDialogEvent.VOLUME_DIALOG_SETTINGS_CLICK);
+            } else if (tag == EVENT_ODI_CAPTIONS_CLICK) {
+                sUiEventLogger.log(VolumeDialogEvent.VOLUME_DIALOG_ODI_CAPTIONS_CLICKED);
+            } else if (tag == EVENT_ODI_CAPTIONS_TOOLTIP_CLICK) {
+                sUiEventLogger.log(VolumeDialogEvent.VOLUME_DIALOG_ODI_CAPTIONS_TOOLTIP_CLICKED);
             }
             return sb.toString();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 929b91c..0ff308e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -120,7 +120,6 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.Dumpable;
 import com.android.systemui.Prefs;
-import com.android.systemui.res.R;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
@@ -129,6 +128,7 @@
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.plugins.VolumeDialogController.State;
 import com.android.systemui.plugins.VolumeDialogController.StreamState;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DevicePostureController;
@@ -287,7 +287,7 @@
     private boolean mIsAnimatingDismiss = false;
     private boolean mHasSeenODICaptionsTooltip;
     private ViewStub mODICaptionsTooltipViewStub;
-    private View mODICaptionsTooltipView = null;
+    @VisibleForTesting View mODICaptionsTooltipView = null;
 
     private final boolean mUseBackgroundBlur;
     private Consumer<Boolean> mCrossWindowBlurEnabledListener;
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index 9dca013..fdf59664 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -109,6 +109,7 @@
         private WallpaperManager mWallpaperManager;
         private final WallpaperLocalColorExtractor mWallpaperLocalColorExtractor;
         private SurfaceHolder mSurfaceHolder;
+        private boolean mDrawn = false;
         @VisibleForTesting
         static final int MIN_SURFACE_WIDTH = 128;
         @VisibleForTesting
@@ -125,8 +126,6 @@
         private int mBitmapUsages = 0;
         private final Object mLock = new Object();
 
-        private boolean mIsLockscreenLiveWallpaperEnabled;
-
         CanvasEngine() {
             super();
             setFixedSizeAllowed(true);
@@ -170,12 +169,8 @@
                 Log.d(TAG, "onCreate");
             }
             mWallpaperManager = getDisplayContext().getSystemService(WallpaperManager.class);
-            mIsLockscreenLiveWallpaperEnabled = mWallpaperManager
-                    .isLockscreenLiveWallpaperEnabled();
             mSurfaceHolder = surfaceHolder;
-            Rect dimensions = mIsLockscreenLiveWallpaperEnabled
-                    ? mWallpaperManager.peekBitmapDimensions(getSourceFlag(), true)
-                    : mWallpaperManager.peekBitmapDimensions();
+            Rect dimensions = mWallpaperManager.peekBitmapDimensions(getSourceFlag(), true);
             int width = Math.max(MIN_SURFACE_WIDTH, dimensions.width());
             int height = Math.max(MIN_SURFACE_HEIGHT, dimensions.height());
             mSurfaceHolder.setFixedSize(width, height);
@@ -239,6 +234,7 @@
 
         private void drawFrameSynchronized() {
             synchronized (mLock) {
+                if (mDrawn) return;
                 drawFrameInternal();
             }
         }
@@ -276,6 +272,7 @@
                 Rect dest = mSurfaceHolder.getSurfaceFrame();
                 try {
                     canvas.drawBitmap(bitmap, null, dest, null);
+                    mDrawn = true;
                 } finally {
                     surface.unlockCanvasAndPost(canvas);
                 }
@@ -324,10 +321,8 @@
             boolean loadSuccess = false;
             Bitmap bitmap;
             try {
-                bitmap = mIsLockscreenLiveWallpaperEnabled
-                        ? mWallpaperManager.getBitmapAsUser(
-                                mUserTracker.getUserId(), false, getSourceFlag(), true)
-                        : mWallpaperManager.getBitmapAsUser(mUserTracker.getUserId(), false);
+                bitmap = mWallpaperManager.getBitmapAsUser(
+                        mUserTracker.getUserId(), false, getSourceFlag(), true);
                 if (bitmap != null
                         && bitmap.getByteCount() > RecordingCanvas.MAX_BITMAP_SIZE) {
                     throw new RuntimeException("Wallpaper is too large to draw!");
@@ -338,18 +333,11 @@
                 // be loaded, we will go into a cycle. Don't do a build where the
                 // default wallpaper can't be loaded.
                 Log.w(TAG, "Unable to load wallpaper!", exception);
-                if (mIsLockscreenLiveWallpaperEnabled) {
-                    mWallpaperManager.clearWallpaper(getWallpaperFlags(), mUserTracker.getUserId());
-                } else {
-                    mWallpaperManager.clearWallpaper(
-                            WallpaperManager.FLAG_SYSTEM, mUserTracker.getUserId());
-                }
+                mWallpaperManager.clearWallpaper(getWallpaperFlags(), mUserTracker.getUserId());
 
                 try {
-                    bitmap = mIsLockscreenLiveWallpaperEnabled
-                            ? mWallpaperManager.getBitmapAsUser(
-                                    mUserTracker.getUserId(), false, getSourceFlag(), true)
-                            : mWallpaperManager.getBitmapAsUser(mUserTracker.getUserId(), false);
+                    bitmap = mWallpaperManager.getBitmapAsUser(
+                            mUserTracker.getUserId(), false, getSourceFlag(), true);
                 } catch (RuntimeException | OutOfMemoryError e) {
                     Log.w(TAG, "Unable to load default wallpaper!", e);
                     bitmap = null;
@@ -370,9 +358,7 @@
                     mBitmap.recycle();
                 }
                 mBitmap = bitmap;
-                mWideColorGamut = mIsLockscreenLiveWallpaperEnabled
-                        ? mWallpaperManager.wallpaperSupportsWcg(getSourceFlag())
-                        : mWallpaperManager.wallpaperSupportsWcg(WallpaperManager.FLAG_SYSTEM);
+                mWideColorGamut = mWallpaperManager.wallpaperSupportsWcg(getSourceFlag());
 
                 // +2 usages for the color extraction and the delayed unload.
                 mBitmapUsages += 2;
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 897c4da..1e801ae 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -33,6 +33,7 @@
 import android.graphics.Rect;
 import android.inputmethodservice.InputMethodService;
 import android.os.IBinder;
+import android.util.Log;
 import android.view.Display;
 import android.view.KeyEvent;
 
@@ -66,6 +67,7 @@
 import com.android.wm.shell.sysui.ShellInterface;
 
 import java.io.PrintWriter;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Optional;
 import java.util.concurrent.Executor;
@@ -371,6 +373,13 @@
 
     @Override
     public void dump(PrintWriter pw, String[] args) {
+        Log.d(TAG, "Dumping with args: " + String.join(", ", args));
+
+        // Strip out the SysUI "dependency" arg before sending to WMShell
+        if (args[0].equals("dependency")) {
+            args = Arrays.copyOfRange(args, 1, args.length);
+        }
+
         // Handle commands if provided
         if (mShell.handleCommand(args, pw)) {
             return;
diff --git a/packages/SystemUI/tests/src/com/android/SysUITestModule.kt b/packages/SystemUI/tests/src/com/android/SysUITestModule.kt
index ea74510..0b3c3b4 100644
--- a/packages/SystemUI/tests/src/com/android/SysUITestModule.kt
+++ b/packages/SystemUI/tests/src/com/android/SysUITestModule.kt
@@ -16,10 +16,12 @@
 package com.android
 
 import android.content.Context
+import android.content.res.Resources
 import com.android.systemui.FakeSystemUiModule
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
 import dagger.Module
 import dagger.Provides
 
@@ -36,6 +38,9 @@
 
     @Provides @Application fun provideAppContext(test: SysuiTestCase): Context = test.context
 
+    @Provides @Main
+    fun provideResources(@Application context: Context): Resources = context.resources
+
     @Provides
     fun provideBroadcastDispatcher(test: SysuiTestCase): BroadcastDispatcher =
         test.fakeBroadcastDispatcher
diff --git a/packages/SystemUI/tests/src/com/android/TestMocksModule.kt b/packages/SystemUI/tests/src/com/android/TestMocksModule.kt
index 167e341..ff1d5b2 100644
--- a/packages/SystemUI/tests/src/com/android/TestMocksModule.kt
+++ b/packages/SystemUI/tests/src/com/android/TestMocksModule.kt
@@ -22,7 +22,9 @@
 import com.android.internal.logging.MetricsLogger
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardViewController
 import com.android.systemui.GuestResumeSessionReceiver
+import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.demomode.DemoModeController
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.ScreenLifecycle
@@ -31,6 +33,7 @@
 import com.android.systemui.log.dagger.BroadcastDispatcherLog
 import com.android.systemui.log.dagger.SceneFrameworkLog
 import com.android.systemui.media.controls.ui.MediaHierarchyManager
+import com.android.systemui.model.SysUiState
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.DarkIconDispatcher
 import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -86,6 +89,9 @@
     @get:Provides val statusBarStateController: SysuiStatusBarStateController = mock(),
     @get:Provides val statusBarWindowController: StatusBarWindowController = mock(),
     @get:Provides val wakefulnessLifecycle: WakefulnessLifecycle = mock(),
+    @get:Provides val keyguardViewController: KeyguardViewController = mock(),
+    @get:Provides val dialogLaunchAnimator: DialogLaunchAnimator = mock(),
+    @get:Provides val sysuiState: SysUiState = mock(),
 
     // log buffers
     @get:[Provides BroadcastDispatcherLog]
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
index 81fef7a..b31f630a4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
@@ -35,9 +35,12 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.settings.FakeSettings
+import java.io.PrintWriter
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Before
@@ -45,26 +48,21 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
-import java.io.PrintWriter
 
 @SmallTest
 class ActiveUnlockConfigTest : SysuiTestCase() {
     private lateinit var secureSettings: FakeSettings
-    @Mock
-    private lateinit var contentResolver: ContentResolver
-    @Mock
-    private lateinit var handler: Handler
-    @Mock
-    private lateinit var dumpManager: DumpManager
-    @Mock
-    private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var contentResolver: ContentResolver
+    @Mock private lateinit var handler: Handler
+    @Mock private lateinit var dumpManager: DumpManager
+    @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var mockPrintWriter: PrintWriter
 
-    @Captor
-    private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver>
+    @Captor private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver>
 
     private lateinit var activeUnlockConfig: ActiveUnlockConfig
     private var currentUser: Int = 0
@@ -73,14 +71,16 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        currentUser = KeyguardUpdateMonitor.getCurrentUser()
+        whenever(selectedUserInteractor.getSelectedUserId()).thenReturn(currentUser)
         secureSettings = FakeSettings()
-        activeUnlockConfig = ActiveUnlockConfig(
+        activeUnlockConfig =
+            ActiveUnlockConfig(
                 handler,
                 secureSettings,
                 contentResolver,
+                selectedUserInteractor,
                 dumpManager
-        )
+            )
     }
 
     @Test
@@ -92,8 +92,9 @@
     fun onWakeupSettingChanged() {
         // GIVEN no active unlock settings enabled
         assertFalse(
-                activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
-                        ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE)
+            activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE
+            )
         )
 
         // WHEN unlock on wake is allowed
@@ -102,16 +103,19 @@
 
         // THEN active unlock triggers allowed on: wake, unlock-intent, and biometric failure
         assertTrue(
-                activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
-                        ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE)
+            activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE
+            )
         )
         assertTrue(
-                activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
-                        ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT)
+            activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT
+            )
         )
         assertTrue(
-                activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
-                        ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL)
+            activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL
+            )
         )
     }
 
@@ -119,8 +123,9 @@
     fun onUnlockIntentSettingChanged() {
         // GIVEN no active unlock settings enabled
         assertFalse(
-                activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
-                        ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT)
+            activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT
+            )
         )
 
         // WHEN unlock on biometric failed is allowed
@@ -128,12 +133,21 @@
         updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT))
 
         // THEN active unlock triggers allowed on: biometric failure ONLY
-        assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
-                ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE))
-        assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
-                ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT))
-        assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
-                ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL))
+        assertFalse(
+            activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE
+            )
+        )
+        assertTrue(
+            activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT
+            )
+        )
+        assertTrue(
+            activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL
+            )
+        )
     }
 
     @Test
@@ -141,24 +155,39 @@
         // GIVEN no active unlock settings enabled and triggering unlock intent on biometric
         // enrollment setting is disabled (empty string is disabled, null would use the default)
         secureSettings.putStringForUser(
-                ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, "", currentUser)
-        updateSetting(secureSettings.getUriFor(
-            ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED
-        ))
-        assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
-                ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL))
+            ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
+            "",
+            currentUser
+        )
+        updateSetting(
+            secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED)
+        )
+        assertFalse(
+            activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL
+            )
+        )
 
         // WHEN unlock on biometric failed is allowed
         secureSettings.putIntForUser(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 1, currentUser)
         updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL))
 
         // THEN active unlock triggers allowed on: biometric failure ONLY
-        assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
-                ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE))
-        assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
-                ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT))
-        assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
-                ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL))
+        assertFalse(
+            activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE
+            )
+        )
+        assertFalse(
+            activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT
+            )
+        )
+        assertTrue(
+            activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL
+            )
+        )
     }
 
     @Test
@@ -168,16 +197,21 @@
         updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL))
 
         // WHEN face error timeout (3), allow trigger active unlock
-        secureSettings.putStringForUser(
-            ACTIVE_UNLOCK_ON_FACE_ERRORS, "3", currentUser)
+        secureSettings.putStringForUser(ACTIVE_UNLOCK_ON_FACE_ERRORS, "3", currentUser)
         updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ERRORS))
 
         // THEN active unlock triggers allowed on error TIMEOUT
-        assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceError(
-                BiometricFaceConstants.FACE_ERROR_TIMEOUT))
+        assertTrue(
+            activeUnlockConfig.shouldRequestActiveUnlockOnFaceError(
+                BiometricFaceConstants.FACE_ERROR_TIMEOUT
+            )
+        )
 
-        assertFalse(activeUnlockConfig.shouldRequestActiveUnlockOnFaceError(
-                BiometricFaceConstants.FACE_ERROR_CANCELED))
+        assertFalse(
+            activeUnlockConfig.shouldRequestActiveUnlockOnFaceError(
+                BiometricFaceConstants.FACE_ERROR_CANCELED
+            )
+        )
     }
 
     @Test
@@ -189,21 +223,34 @@
         // WHEN face acquiredMsg DARK_GLASSESand MOUTH_COVERING are allowed to trigger
         secureSettings.putStringForUser(
             ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO,
-                "${BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED}" +
-                        "|${BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED}",
-            currentUser)
+            "${BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED}" +
+                "|${BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED}",
+            currentUser
+        )
         updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO))
 
         // THEN active unlock triggers allowed on acquired messages DARK_GLASSES & MOUTH_COVERING
-        assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
-                BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED))
-        assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
-                BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED))
+        assertTrue(
+            activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
+                BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED
+            )
+        )
+        assertTrue(
+            activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
+                BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED
+            )
+        )
 
-        assertFalse(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
-                BiometricFaceConstants.FACE_ACQUIRED_GOOD))
-        assertFalse(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
-                BiometricFaceConstants.FACE_ACQUIRED_NOT_DETECTED))
+        assertFalse(
+            activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
+                BiometricFaceConstants.FACE_ACQUIRED_GOOD
+            )
+        )
+        assertFalse(
+            activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
+                BiometricFaceConstants.FACE_ACQUIRED_NOT_DETECTED
+            )
+        )
     }
 
     @Test
@@ -221,14 +268,19 @@
 
         secureSettings.putStringForUser(
             ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
-            "${ActiveUnlockConfig.BiometricType.NONE.intValue}", currentUser)
-        updateSetting(secureSettings.getUriFor(
-            ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED
-        ))
+            "${ActiveUnlockConfig.BiometricType.NONE.intValue}",
+            currentUser
+        )
+        updateSetting(
+            secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED)
+        )
 
         // THEN active unlock triggers allowed on unlock intent
-        assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
-                ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT))
+        assertTrue(
+            activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT
+            )
+        )
     }
 
     @Test
@@ -245,33 +297,43 @@
         // WHEN unlock intent is allowed when ONLY fingerprint is enrolled or NO biometircs
         // are enrolled
         secureSettings.putStringForUser(
-                ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
-                "${ActiveUnlockConfig.BiometricType.ANY_FACE.intValue}" +
-                        "|${ActiveUnlockConfig.BiometricType.ANY_FINGERPRINT.intValue}",
-            currentUser)
-        updateSetting(secureSettings.getUriFor(
-            ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED
-        ))
+            ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
+            "${ActiveUnlockConfig.BiometricType.ANY_FACE.intValue}" +
+                "|${ActiveUnlockConfig.BiometricType.ANY_FINGERPRINT.intValue}",
+            currentUser
+        )
+        updateSetting(
+            secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED)
+        )
 
         // THEN active unlock triggers NOT allowed on unlock intent
-        assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
-                ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT))
+        assertFalse(
+            activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT
+            )
+        )
 
         // WHEN fingerprint ONLY enrolled
         `when`(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(false)
         `when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(true)
 
         // THEN active unlock triggers allowed on unlock intent
-        assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
-                ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT))
+        assertTrue(
+            activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT
+            )
+        )
 
         // WHEN face ONLY enrolled
         `when`(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(true)
         `when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(false)
 
         // THEN active unlock triggers allowed on unlock intent
-        assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
-                ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT))
+        assertTrue(
+            activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT
+            )
+        )
     }
 
     @Test
@@ -280,7 +342,8 @@
         secureSettings.putIntForUser(
             ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS,
             PowerManager.WAKE_REASON_LIFT,
-            currentUser)
+            currentUser
+        )
         updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS))
 
         // THEN only WAKE_REASON_LIFT is considered an unlock intent
@@ -299,16 +362,18 @@
         secureSettings.putStringForUser(
             ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS,
             PowerManager.WAKE_REASON_LIFT.toString() +
-                    "|" +
-                    PowerManager.WAKE_REASON_TAP.toString(),
+                "|" +
+                PowerManager.WAKE_REASON_TAP.toString(),
             currentUser
         )
         updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS))
 
         // THEN WAKE_REASON_LIFT and WAKE_REASON TAP are considered an unlock intent
         for (wakeReason in 0..WAKE_REASON_BIOMETRIC) {
-            if (wakeReason == PowerManager.WAKE_REASON_LIFT ||
-                wakeReason == PowerManager.WAKE_REASON_TAP) {
+            if (
+                wakeReason == PowerManager.WAKE_REASON_LIFT ||
+                    wakeReason == PowerManager.WAKE_REASON_TAP
+            ) {
                 assertTrue(activeUnlockConfig.isWakeupConsideredUnlockIntent(wakeReason))
             } else {
                 assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(wakeReason))
@@ -316,26 +381,36 @@
         }
         assertTrue(activeUnlockConfig.isWakeupConsideredUnlockIntent(PowerManager.WAKE_REASON_LIFT))
         assertTrue(activeUnlockConfig.isWakeupConsideredUnlockIntent(PowerManager.WAKE_REASON_TAP))
-        assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(
-            PowerManager.WAKE_REASON_UNFOLD_DEVICE))
+        assertFalse(
+            activeUnlockConfig.isWakeupConsideredUnlockIntent(
+                PowerManager.WAKE_REASON_UNFOLD_DEVICE
+            )
+        )
     }
 
     @Test
     fun isWakeupConsideredUnlockIntent_emptyValues() {
         // GIVEN lift and tap are considered an unlock intent
-        secureSettings.putStringForUser(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS, " ",
-            currentUser)
+        secureSettings.putStringForUser(
+            ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS,
+            " ",
+            currentUser
+        )
         updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS))
 
         // THEN no wake up gestures are considered an unlock intent
         for (wakeReason in 0..WAKE_REASON_BIOMETRIC) {
             assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(wakeReason))
         }
-        assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(
-            PowerManager.WAKE_REASON_LIFT))
+        assertFalse(
+            activeUnlockConfig.isWakeupConsideredUnlockIntent(PowerManager.WAKE_REASON_LIFT)
+        )
         assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(PowerManager.WAKE_REASON_TAP))
-        assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(
-            PowerManager.WAKE_REASON_UNFOLD_DEVICE))
+        assertFalse(
+            activeUnlockConfig.isWakeupConsideredUnlockIntent(
+                PowerManager.WAKE_REASON_UNFOLD_DEVICE
+            )
+        )
     }
 
     @Test
@@ -343,11 +418,12 @@
         verifyRegisterSettingObserver()
 
         // GIVEN lift is considered an unlock intent
-        secureSettings.putStringForUser(ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD,
-            PowerManager.WAKE_REASON_LIFT.toString(), currentUser)
-        updateSetting(secureSettings.getUriFor(
-            ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD
-        ))
+        secureSettings.putStringForUser(
+            ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD,
+            PowerManager.WAKE_REASON_LIFT.toString(),
+            currentUser
+        )
+        updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD))
 
         // THEN only WAKE_REASON_LIFT is considered an unlock intent
         for (wakeReason in 0..WAKE_REASON_BIOMETRIC) {
@@ -364,11 +440,12 @@
         verifyRegisterSettingObserver()
 
         // GIVEN lift and tap are considered an unlock intent
-        secureSettings.putStringForUser(ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD,
-            " ", currentUser)
-        updateSetting(secureSettings.getUriFor(
-            ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD
-        ))
+        secureSettings.putStringForUser(
+            ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD,
+            " ",
+            currentUser
+        )
+        updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD))
 
         // THEN no wake up gestures are considered an unlock intent
         for (wakeReason in 0..WAKE_REASON_BIOMETRIC) {
@@ -381,20 +458,21 @@
         verifyRegisterSettingObserver()
 
         // GIVEN lift and tap are considered an unlock intent
-        secureSettings.putStringForUser(ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD,
+        secureSettings.putStringForUser(
+            ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD,
             PowerManager.WAKE_REASON_LIFT.toString() +
-                    "|" +
-                    PowerManager.WAKE_REASON_TAP.toString(),
+                "|" +
+                PowerManager.WAKE_REASON_TAP.toString(),
             currentUser
         )
-        updateSetting(secureSettings.getUriFor(
-            ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD
-        ))
+        updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD))
 
         // THEN WAKE_REASON_LIFT and WAKE_REASON TAP are considered an unlock intent
         for (wakeReason in 0..WAKE_REASON_BIOMETRIC) {
-            if (wakeReason == PowerManager.WAKE_REASON_LIFT ||
-                wakeReason == PowerManager.WAKE_REASON_TAP) {
+            if (
+                wakeReason == PowerManager.WAKE_REASON_LIFT ||
+                    wakeReason == PowerManager.WAKE_REASON_TAP
+            ) {
                 assertTrue(activeUnlockConfig.shouldWakeupForceDismissKeyguard(wakeReason))
             } else {
                 assertFalse(activeUnlockConfig.shouldWakeupForceDismissKeyguard(wakeReason))
@@ -405,13 +483,16 @@
     @Test
     fun dump_onUnlockIntentWhenBiometricEnrolled_invalidNum_noArrayOutOfBoundsException() {
         // GIVEN an invalid input (-1)
-        secureSettings.putStringForUser(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
-            "-1", currentUser)
+        secureSettings.putStringForUser(
+            ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
+            "-1",
+            currentUser
+        )
 
         // WHEN the setting updates
-        updateSetting(secureSettings.getUriFor(
-            ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED
-        ))
+        updateSetting(
+            secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED)
+        )
 
         // THEN no exception thrown
         activeUnlockConfig.dump(mockPrintWriter, emptyArray())
@@ -419,12 +500,7 @@
 
     private fun updateSetting(uri: Uri) {
         verifyRegisterSettingObserver()
-        settingsObserverCaptor.value.onChange(
-                false,
-                listOf(uri),
-                0,
-                0 /* flags */
-        )
+        settingsObserverCaptor.value.onChange(false, listOf(uri), 0, 0 /* flags */)
     }
 
     private fun verifyRegisterSettingObserver() {
@@ -433,19 +509,21 @@
         verifyRegisterSettingObserver(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL))
         verifyRegisterSettingObserver(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ERRORS))
         verifyRegisterSettingObserver(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO))
-        verifyRegisterSettingObserver(secureSettings.getUriFor(
-            ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED
-        ))
-        verifyRegisterSettingObserver(secureSettings.getUriFor(
-            ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS
-        ))
+        verifyRegisterSettingObserver(
+            secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED)
+        )
+        verifyRegisterSettingObserver(
+            secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS)
+        )
     }
 
     private fun verifyRegisterSettingObserver(uri: Uri) {
-        verify(contentResolver).registerContentObserver(
+        verify(contentResolver)
+            .registerContentObserver(
                 eq(uri),
                 eq(false),
                 capture(settingsObserverCaptor),
-                eq(UserHandle.USER_ALL))
+                eq(UserHandle.USER_ALL)
+            )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
index d506584..cdd0eb0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
@@ -50,6 +50,7 @@
 
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 import org.junit.After;
 import org.junit.Before;
@@ -64,7 +65,7 @@
 @SmallTest
 public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase {
 
-    private static final int TARGET_USER_ID = KeyguardUpdateMonitor.getCurrentUser();
+    private static final int TARGET_USER_ID = 0;
 
     private AdminSecondaryLockScreenController mTestController;
     private ComponentName mComponentName;
@@ -80,12 +81,15 @@
     private KeyguardSecurityCallback mKeyguardCallback;
     @Mock
     private KeyguardUpdateMonitor mUpdateMonitor;
+    @Mock
+    private SelectedUserInteractor mSelectedUserInteractor;
 
     private SurfaceControlViewHost.SurfacePackage mSurfacePackage;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(TARGET_USER_ID);
 
         mKeyguardSecurityContainer = spy(new KeyguardSecurityContainer(mContext));
         mKeyguardSecurityContainer.setId(View.generateViewId());
@@ -106,7 +110,8 @@
                 new Binder())).getSurfacePackage();
 
         mTestController = new AdminSecondaryLockScreenController.Factory(
-                mContext, mKeyguardSecurityContainer, mUpdateMonitor, mHandler)
+                mContext, mKeyguardSecurityContainer, mUpdateMonitor, mHandler,
+                mSelectedUserInteractor)
                 .create(mKeyguardCallback);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
index 30fed0b..c61b11a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.shade.ShadeController
 import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
@@ -59,6 +60,7 @@
     @Mock lateinit var metricsLogger: MetricsLogger
     @Mock lateinit var lockPatternUtils: LockPatternUtils
     @Mock lateinit var packageManager: PackageManager
+    @Mock lateinit var mSelectedUserInteractor: SelectedUserInteractor
     val fakeSystemClock = FakeSystemClock()
     val mainExecutor = FakeExecutor(fakeSystemClock)
     val backgroundExecutor = FakeExecutor(fakeSystemClock)
@@ -79,7 +81,8 @@
                 metricsLogger,
                 lockPatternUtils,
                 mainExecutor,
-                backgroundExecutor
+                backgroundExecutor,
+                mSelectedUserInteractor,
             )
         context.setMockPackageManager(packageManager)
         Mockito.`when`(emergencyButton.context).thenReturn(context)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index 42f65f6..7f20d9a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -37,12 +37,13 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardAbsKeyInputView.KeyDownListener;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.res.R;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -81,6 +82,8 @@
     private EmergencyButtonController mEmergencyButtonController;
 
     private FakeFeatureFlags mFeatureFlags;
+    @Mock
+    private SelectedUserInteractor mSelectedUserInteractor;
     private KeyguardAbsKeyInputViewController mKeyguardAbsKeyInputViewController;
 
     @Before
@@ -105,7 +108,7 @@
         return new KeyguardAbsKeyInputViewController(mAbsKeyInputView,
                 mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
                 mKeyguardMessageAreaControllerFactory, mLatencyTracker, mFalsingCollector,
-                mEmergencyButtonController, mFeatureFlags) {
+                mEmergencyButtonController, mFeatureFlags, mSelectedUserInteractor) {
             @Override
             void resetState() {
             }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
index 91b544b..634dac1 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
@@ -25,6 +25,7 @@
 import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.SessionTracker
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -34,8 +35,8 @@
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
@@ -50,6 +51,8 @@
     lateinit var sessionTracker: SessionTracker
     @Mock
     lateinit var sessionId: InstanceId
+    @Mock
+    lateinit var mSelectedUserInteractor: SelectedUserInteractor
 
     @Captor
     lateinit var updateMonitorCallbackCaptor: ArgumentCaptor<KeyguardUpdateMonitorCallback>
@@ -65,7 +68,8 @@
         keyguardBiometricLockoutLogger = KeyguardBiometricLockoutLogger(
                 uiEventLogger,
                 keyguardUpdateMonitor,
-                sessionTracker)
+                sessionTracker,
+                mSelectedUserInteractor)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index d8a2c5f..3fbcf6d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -24,12 +24,13 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.util.LatencyTracker
 import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.DevicePostureController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.mockito.whenever
 import org.junit.Before
@@ -62,6 +63,7 @@
     @Mock lateinit var mainExecutor: DelayableExecutor
     @Mock lateinit var falsingCollector: FalsingCollector
     @Mock lateinit var keyguardViewController: KeyguardViewController
+    @Mock lateinit var mSelectedUserInteractor: SelectedUserInteractor
     @Mock private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea
     @Mock
     private lateinit var mKeyguardMessageAreaController:
@@ -106,7 +108,8 @@
                 falsingCollector,
                 keyguardViewController,
                 postureController,
-                fakeFeatureFlags
+                fakeFeatureFlags,
+                mSelectedUserInteractor,
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index dc1618d..74c9225 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -24,15 +24,16 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.util.LatencyTracker
 import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.classifier.FalsingCollectorFake
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.DevicePostureController
 import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED
 import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
@@ -75,6 +76,9 @@
         KeyguardMessageAreaController.Factory
 
     @Mock
+    private lateinit var mSelectedUserInteractor: SelectedUserInteractor
+
+    @Mock
     private lateinit var mKeyguardMessageAreaController:
         KeyguardMessageAreaController<BouncerKeyguardMessageArea>
 
@@ -108,7 +112,8 @@
                 mEmergencyButtonController,
                 mKeyguardMessageAreaControllerFactory,
                 mPostureController,
-                fakeFeatureFlags
+                fakeFeatureFlags,
+                mSelectedUserInteractor
             )
         mKeyguardPatternView.onAttachedToWindow()
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index 4a24e4a..d41c249 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -34,10 +34,10 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.classifier.FalsingCollectorFake;
-import com.android.systemui.classifier.SingleTapClassifier;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.res.R;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -76,11 +76,11 @@
     private EmergencyButtonController mEmergencyButtonController;
     private FalsingCollector mFalsingCollector = new FalsingCollectorFake();
     @Mock
-    private SingleTapClassifier mSingleTapClassifier;
-    @Mock
     private View mDeleteButton;
     @Mock
     private View mOkButton;
+    @Mock
+    private SelectedUserInteractor mSelectedUserInteractor;
     private NumPadKey[] mButtons = new NumPadKey[]{};
 
     private KeyguardPinBasedInputViewController mKeyguardPinViewController;
@@ -108,7 +108,8 @@
         mKeyguardPinViewController = new KeyguardPinBasedInputViewController(mPinBasedInputView,
                 mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
                 mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener,
-                mEmergencyButtonController, mFalsingCollector, featureFlags) {
+                mEmergencyButtonController, mFalsingCollector, featureFlags,
+                mSelectedUserInteractor) {
             @Override
             public void onResume(int reason) {
                 super.onResume(reason);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 9df4dd4..80d45bc 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -25,15 +25,16 @@
 import com.android.internal.util.LatencyTracker
 import com.android.internal.widget.LockPatternUtils
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.classifier.FalsingCollectorFake
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.DevicePostureController
 import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED
 import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -84,6 +85,7 @@
     @Mock private val mEmergencyButtonController: EmergencyButtonController? = null
     private val falsingCollector: FalsingCollector = FalsingCollectorFake()
     @Mock lateinit var postureController: DevicePostureController
+    @Mock lateinit var mSelectedUserInteractor: SelectedUserInteractor
 
     @Mock lateinit var featureFlags: FeatureFlags
     @Mock lateinit var passwordTextView: PasswordTextView
@@ -133,7 +135,8 @@
             mEmergencyButtonController,
             falsingCollector,
             postureController,
-            featureFlags
+            featureFlags,
+            mSelectedUserInteractor,
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 20d4eb9..fda4133 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -63,7 +63,7 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.statusbar.policy.UserSwitcherController
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.kotlin.JavaAdapter
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argThat
@@ -136,7 +136,7 @@
     @Mock private lateinit var telephonyManager: TelephonyManager
     @Mock private lateinit var viewMediatorCallback: ViewMediatorCallback
     @Mock private lateinit var audioManager: AudioManager
-    @Mock private lateinit var userInteractor: UserInteractor
+    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
     @Mock private lateinit var faceAuthAccessibilityDelegate: FaceAuthAccessibilityDelegate
     @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
     @Mock private lateinit var postureController: DevicePostureController
@@ -215,10 +215,10 @@
                 null,
                 keyguardViewController,
                 postureController,
-                featureFlags
+                featureFlags,
+                mSelectedUserInteractor,
             )
 
-        whenever(userInteractor.getSelectedUserId()).thenReturn(TARGET_USER_ID)
         sceneTestUtils = SceneTestUtils(this)
         sceneInteractor = sceneTestUtils.sceneInteractor()
         keyguardTransitionInteractor =
@@ -260,7 +260,7 @@
                 mock(),
                 mock(),
                 { JavaAdapter(sceneTestUtils.testScope.backgroundScope) },
-                userInteractor,
+                mSelectedUserInteractor,
                 deviceProvisionedController,
                 faceAuthAccessibilityDelegate,
                 keyguardTransitionInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
index 4290b8b..94c3bde 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
@@ -24,11 +24,12 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.util.LatencyTracker
 import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.res.R
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.any
 import org.junit.Before
 import org.junit.Test
@@ -58,6 +59,7 @@
     @Mock private lateinit var telephonyManager: TelephonyManager
     @Mock private lateinit var falsingCollector: FalsingCollector
     @Mock private lateinit var emergencyButtonController: EmergencyButtonController
+    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
     @Mock
     private lateinit var keyguardMessageAreaController:
         KeyguardMessageAreaController<BouncerKeyguardMessageArea>
@@ -90,6 +92,7 @@
                 falsingCollector,
                 emergencyButtonController,
                 fakeFeatureFlags,
+                mSelectedUserInteractor
             )
         underTest.init()
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
index 31ee641..7b1f302 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
@@ -24,11 +24,12 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.util.LatencyTracker
 import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.res.R
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.any
 import org.junit.Before
 import org.junit.Test
@@ -54,6 +55,7 @@
     @Mock private lateinit var telephonyManager: TelephonyManager
     @Mock private lateinit var falsingCollector: FalsingCollector
     @Mock private lateinit var emergencyButtonController: EmergencyButtonController
+    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
     @Mock
     private lateinit var keyguardMessageAreaController:
         KeyguardMessageAreaController<BouncerKeyguardMessageArea>
@@ -89,6 +91,7 @@
                 falsingCollector,
                 emergencyButtonController,
                 fakeFeatureFlags,
+                mSelectedUserInteractor,
             )
         underTest.init()
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 47be236..aabdcb7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -38,7 +38,6 @@
 import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING_RESTARTING;
 import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
 import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT;
-import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
@@ -82,7 +81,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
-import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.hardware.SensorPrivacyManager;
 import android.hardware.biometrics.BiometricAuthenticator;
@@ -157,8 +155,8 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.telephony.TelephonyListenerManager;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.settings.GlobalSettings;
-import com.android.systemui.util.settings.SecureSettings;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -240,8 +238,6 @@
     @Mock
     private BroadcastDispatcher mBroadcastDispatcher;
     @Mock
-    private SecureSettings mSecureSettings;
-    @Mock
     private TelephonyManager mTelephonyManager;
     @Mock
     private SensorPrivacyManager mSensorPrivacyManager;
@@ -278,18 +274,17 @@
     @Mock
     private UsbPortStatus mUsbPortStatus;
     @Mock
-    private Uri mURI;
-    @Mock
     private TaskStackChangeListeners mTaskStackChangeListeners;
     @Mock
     private IActivityTaskManager mActivityTaskManager;
     @Mock
     private WakefulnessLifecycle mWakefulness;
+    @Mock
+    private SelectedUserInteractor mSelectedUserInteractor;
 
     private List<FaceSensorPropertiesInternal> mFaceSensorProperties;
     private List<FingerprintSensorPropertiesInternal> mFingerprintSensorProperties;
     private final int mCurrentUserId = 100;
-    private final UserInfo mCurrentUserInfo = new UserInfo(mCurrentUserId, "Test user", 0);
 
     @Captor
     private ArgumentCaptor<IBiometricEnabledOnKeyguardCallback>
@@ -337,8 +332,8 @@
                 .startMocking();
         ExtendedMockito.doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
                 .when(SubscriptionManager::getDefaultSubscriptionId);
-        KeyguardUpdateMonitor.setCurrentUser(mCurrentUserId);
-        when(mUserTracker.getUserId()).thenReturn(mCurrentUserId);
+        when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(mCurrentUserId);
+        when(mSelectedUserInteractor.getSelectedUserId(anyBoolean())).thenReturn(mCurrentUserId);
 
         mContext.getOrCreateTestableResources().addOverride(
                 com.android.systemui.res.R.integer.config_face_auth_supported_posture,
@@ -351,13 +346,11 @@
 
         mContext.getOrCreateTestableResources().addOverride(com.android.systemui.res
                         .R.array.config_fingerprint_listen_on_occluding_activity_packages,
-                new String[]{ PKG_ALLOWING_FP_LISTEN_ON_OCCLUDING_ACTIVITY });
+                new String[]{PKG_ALLOWING_FP_LISTEN_ON_OCCLUDING_ACTIVITY});
 
         mTestableLooper = TestableLooper.get(this);
         allowTestableLooperAsMainThread();
 
-        when(mSecureSettings.getUriFor(anyString())).thenReturn(mURI);
-
         final ContentResolver contentResolver = mContext.getContentResolver();
         ExtendedMockito.spyOn(contentResolver);
         doNothing().when(contentResolver)
@@ -1005,11 +998,14 @@
     @Test
     public void trustAgentHasTrust() {
         // WHEN user has trust
-        mKeyguardUpdateMonitor.onTrustChanged(true, true, getCurrentUser(), 0, null);
+        mKeyguardUpdateMonitor.onTrustChanged(true, true,
+                mSelectedUserInteractor.getSelectedUserId(), 0, null);
 
         // THEN user is considered as "having trust" and bouncer can be skipped
-        Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser()));
-        Assert.assertTrue(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()));
+        Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(
+                mSelectedUserInteractor.getSelectedUserId()));
+        Assert.assertTrue(mKeyguardUpdateMonitor.getUserCanSkipBouncer(
+                mSelectedUserInteractor.getSelectedUserId()));
     }
 
     @Test
@@ -1027,15 +1023,19 @@
     @Test
     public void trustAgentHasTrust_fingerprintLockout() {
         // GIVEN user has trust
-        mKeyguardUpdateMonitor.onTrustChanged(true, true, getCurrentUser(), 0, null);
-        Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser()));
+        mKeyguardUpdateMonitor.onTrustChanged(true, true,
+                mSelectedUserInteractor.getSelectedUserId(), 0, null);
+        Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(
+                mSelectedUserInteractor.getSelectedUserId()));
 
         // WHEN fingerprint is lock out
         fingerprintErrorTemporaryLockOut();
 
         // THEN user is NOT considered as "having trust" and bouncer cannot be skipped
-        Assert.assertFalse(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser()));
-        Assert.assertFalse(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()));
+        Assert.assertFalse(mKeyguardUpdateMonitor.getUserHasTrust(
+                mSelectedUserInteractor.getSelectedUserId()));
+        Assert.assertFalse(mKeyguardUpdateMonitor.getUserCanSkipBouncer(
+                mSelectedUserInteractor.getSelectedUserId()));
     }
 
     @Test
@@ -1217,7 +1217,7 @@
         mTestableLooper.processAllMessages();
         lockscreenBypassIsAllowed();
         mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, true /* newlyUnlocked */,
-                KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */,
+                mSelectedUserInteractor.getSelectedUserId(), 0 /* flags */,
                 new ArrayList<>());
         keyguardIsVisible();
         verifyFaceAuthenticateCall();
@@ -1249,7 +1249,7 @@
         mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
         mTestableLooper.processAllMessages();
         mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, true /* newlyUnlocked */,
-                KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */, new ArrayList<>());
+                mSelectedUserInteractor.getSelectedUserId(), 0 /* flags */, new ArrayList<>());
         keyguardIsVisible();
         verifyFaceAuthenticateNeverCalled();
     }
@@ -1287,7 +1287,7 @@
     public void testOnFaceAuthenticated_skipsFaceWhenAuthenticated() {
         // test whether face will be skipped if authenticated, so the value of isClass3Biometric
         // doesn't matter here
-        mKeyguardUpdateMonitor.onFaceAuthenticated(KeyguardUpdateMonitor.getCurrentUser(),
+        mKeyguardUpdateMonitor.onFaceAuthenticated(mSelectedUserInteractor.getSelectedUserId(),
                 true /* isClass3Biometric */);
         setKeyguardBouncerVisibility(true);
         mTestableLooper.processAllMessages();
@@ -1333,7 +1333,7 @@
 
     @Test
     public void testGetUserCanSkipBouncer_whenFace() {
-        int user = KeyguardUpdateMonitor.getCurrentUser();
+        int user = mSelectedUserInteractor.getSelectedUserId();
         mKeyguardUpdateMonitor.onFaceAuthenticated(user, true /* isClass3Biometric */);
         assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue();
     }
@@ -1342,14 +1342,14 @@
     public void testGetUserCanSkipBouncer_whenFace_nonStrongAndDisallowed() {
         when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isClass3Biometric */))
                 .thenReturn(false);
-        int user = KeyguardUpdateMonitor.getCurrentUser();
+        int user = mSelectedUserInteractor.getSelectedUserId();
         mKeyguardUpdateMonitor.onFaceAuthenticated(user, false /* isClass3Biometric */);
         assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isFalse();
     }
 
     @Test
     public void testGetUserCanSkipBouncer_whenFingerprint() {
-        int user = KeyguardUpdateMonitor.getCurrentUser();
+        int user = mSelectedUserInteractor.getSelectedUserId();
         mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, true /* isClass3Biometric */);
         assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue();
     }
@@ -1358,7 +1358,7 @@
     public void testGetUserCanSkipBouncer_whenFingerprint_nonStrongAndDisallowed() {
         when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isClass3Biometric */))
                 .thenReturn(false);
-        int user = KeyguardUpdateMonitor.getCurrentUser();
+        int user = mSelectedUserInteractor.getSelectedUserId();
         mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, false /* isClass3Biometric */);
         assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isFalse();
     }
@@ -1372,7 +1372,8 @@
         assertThat(mKeyguardUpdateMonitor.mUserFingerprintAuthenticated.size()).isEqualTo(1);
         assertThat(mKeyguardUpdateMonitor.mUserFaceAuthenticated.size()).isEqualTo(1);
 
-        mKeyguardUpdateMonitor.handleUserSwitching(10 /* user */, () -> {});
+        mKeyguardUpdateMonitor.handleUserSwitching(10 /* user */, () -> {
+        });
         assertThat(mKeyguardUpdateMonitor.mUserFingerprintAuthenticated.size()).isEqualTo(0);
         assertThat(mKeyguardUpdateMonitor.mUserFaceAuthenticated.size()).isEqualTo(0);
     }
@@ -1446,7 +1447,7 @@
 
     @Test
     public void testGetUserCanSkipBouncer_whenTrust() {
-        int user = KeyguardUpdateMonitor.getCurrentUser();
+        int user = mSelectedUserInteractor.getSelectedUserId();
         mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, true /* newlyUnlocked */,
                 user, 0 /* flags */, new ArrayList<>());
         assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue();
@@ -1493,7 +1494,8 @@
     @Test
     public void testIsUserUnlocked() {
         // mUserManager will report the user as unlocked on @Before
-        assertThat(mKeyguardUpdateMonitor.isUserUnlocked(KeyguardUpdateMonitor.getCurrentUser()))
+        assertThat(
+                mKeyguardUpdateMonitor.isUserUnlocked(mSelectedUserInteractor.getSelectedUserId()))
                 .isTrue();
         // Invalid user should not be unlocked.
         int randomUser = 99;
@@ -1502,7 +1504,7 @@
 
     @Test
     public void testTrustUsuallyManaged_whenTrustChanges() {
-        int user = KeyguardUpdateMonitor.getCurrentUser();
+        int user = mSelectedUserInteractor.getSelectedUserId();
         when(mTrustManager.isTrustUsuallyManaged(eq(user))).thenReturn(true);
         mKeyguardUpdateMonitor.onTrustManagedChanged(false /* managed */, user);
         assertThat(mKeyguardUpdateMonitor.isTrustUsuallyManaged(user)).isTrue();
@@ -1510,7 +1512,7 @@
 
     @Test
     public void testTrustUsuallyManaged_resetWhenUserIsRemoved() {
-        int user = KeyguardUpdateMonitor.getCurrentUser();
+        int user = mSelectedUserInteractor.getSelectedUserId();
         when(mTrustManager.isTrustUsuallyManaged(eq(user))).thenReturn(true);
         mKeyguardUpdateMonitor.onTrustManagedChanged(false /* managed */, user);
         assertThat(mKeyguardUpdateMonitor.isTrustUsuallyManaged(user)).isTrue();
@@ -1521,9 +1523,9 @@
 
     @Test
     public void testSecondaryLockscreenRequirement() {
-        KeyguardUpdateMonitor.setCurrentUser(UserHandle.myUserId());
+        when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(UserHandle.myUserId());
         when(mUserTracker.getUserId()).thenReturn(UserHandle.myUserId());
-        int user = KeyguardUpdateMonitor.getCurrentUser();
+        int user = mSelectedUserInteractor.getSelectedUserId();
         String packageName = "fake.test.package";
         String cls = "FakeService";
         ServiceInfo serviceInfo = new ServiceInfo();
@@ -1680,7 +1682,7 @@
         mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD);
 
         // WHEN user loses smart unlock trust
-        when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
+        when(mStrongAuthTracker.getStrongAuthForUser(mSelectedUserInteractor.getSelectedUserId()))
                 .thenReturn(SOME_AUTH_REQUIRED_AFTER_USER_REQUEST);
 
         // THEN we should still listen for udfps
@@ -1724,7 +1726,7 @@
 
         // WHEN trust is enabled (ie: via smartlock)
         mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, true /* newlyUnlocked */,
-                KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */, new ArrayList<>());
+                mSelectedUserInteractor.getSelectedUserId(), 0 /* flags */, new ArrayList<>());
 
         // THEN we shouldn't listen for udfps
         assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(false);
@@ -1738,7 +1740,7 @@
 
         // WHEN face authenticated
         mKeyguardUpdateMonitor.onFaceAuthenticated(
-                KeyguardUpdateMonitor.getCurrentUser(), false);
+                mSelectedUserInteractor.getSelectedUserId(), false);
 
         // THEN we shouldn't listen for udfps
         assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(false);
@@ -1825,7 +1827,7 @@
     public void testShowTrustGrantedMessage_onTrustGranted() {
         // WHEN trust is enabled (ie: via some trust agent) with a trustGranted string
         mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, true /* newlyUnlocked */,
-                KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */,
+                mSelectedUserInteractor.getSelectedUserId(), 0 /* flags */,
                 Arrays.asList("Unlocked by wearable"));
 
         // THEN the showTrustGrantedMessage should be called with the first message
@@ -2256,7 +2258,7 @@
         mKeyguardUpdateMonitor.onTrustChanged(
                 true /* enabled */,
                 true /* newlyUnlocked */,
-                getCurrentUser() /* userId */,
+                mSelectedUserInteractor.getSelectedUserId() /* userId */,
                 TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD /* flags */,
                 null /* trustGrantedMessages */);
 
@@ -2281,7 +2283,7 @@
         mKeyguardUpdateMonitor.onTrustChanged(
                 true /* enabled */,
                 true /* newlyUnlocked */,
-                getCurrentUser() /* userId */,
+                mSelectedUserInteractor.getSelectedUserId() /* userId */,
                 TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD /* flags */,
                 null /* trustGrantedMessages */);
 
@@ -2333,7 +2335,7 @@
         mKeyguardUpdateMonitor.onTrustChanged(
                 true /* enabled */,
                 true /* newlyUnlocked */,
-                getCurrentUser() /* userId */,
+                mSelectedUserInteractor.getSelectedUserId() /* userId */,
                 TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD
                         | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE /* flags */,
                 null /* trustGrantedMessages */);
@@ -2362,7 +2364,7 @@
         mKeyguardUpdateMonitor.onTrustChanged(
                 true /* enabled */,
                 true /* newlyUnlocked */,
-                getCurrentUser() /* userId, not the current userId */,
+                mSelectedUserInteractor.getSelectedUserId() /* userId, not the current userId */,
                 TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER /* flags */,
                 null /* trustGrantedMessages */);
 
@@ -2388,7 +2390,7 @@
         mKeyguardUpdateMonitor.onTrustChanged(
                 true /* enabled */,
                 true /* newlyUnlocked */,
-                getCurrentUser() /* userId, not the current userId */,
+                mSelectedUserInteractor.getSelectedUserId() /* userId, not the current userId */,
                 TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER
                         | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE /* flags */,
                 null /* trustGrantedMessages */);
@@ -2423,7 +2425,8 @@
         // WHEN strong auth changes and device is in user lockdown
         when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
         userDeviceLockDown();
-        mKeyguardUpdateMonitor.notifyStrongAuthAllowedChanged(getCurrentUser());
+        mKeyguardUpdateMonitor.notifyStrongAuthAllowedChanged(
+                mSelectedUserInteractor.getSelectedUserId());
         mTestableLooper.processAllMessages();
 
         // THEN face and fingerprint listening are cancelled
@@ -2451,7 +2454,8 @@
 
         // WHEN non-strong biometric allowed changes
         when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
-        mKeyguardUpdateMonitor.notifyNonStrongBiometricAllowedChanged(getCurrentUser());
+        mKeyguardUpdateMonitor.notifyNonStrongBiometricAllowedChanged(
+                mSelectedUserInteractor.getSelectedUserId());
         mTestableLooper.processAllMessages();
 
         // THEN face and fingerprint listening are cancelled
@@ -2516,13 +2520,15 @@
         keyguardIsVisible();
         keyguardNotGoingAway();
         statusBarShadeIsNotLocked();
-        when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+        when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
+                true);
 
         // WHEN the assistant is visible
         mKeyguardUpdateMonitor.setAssistantVisible(true);
 
         // THEN request unlock with keyguard dismissal
-        verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+        verify(mTrustManager).reportUserRequestedUnlock(
+                eq(mSelectedUserInteractor.getSelectedUserId()),
                 eq(true));
     }
 
@@ -2531,7 +2537,8 @@
             throws RemoteException {
         // GIVEN shouldTriggerActiveUnlock
         bouncerFullyVisible();
-        when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+        when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
+                true);
 
         // GIVEN active unlock triggers on biometric failures
         when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -2542,7 +2549,8 @@
         mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback.onAuthenticationFailed();
 
         // ALWAYS request unlock with a keyguard dismissal
-        verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+        verify(mTrustManager).reportUserRequestedUnlock(
+                eq(mSelectedUserInteractor.getSelectedUserId()),
                 eq(true));
     }
 
@@ -2554,7 +2562,8 @@
         keyguardIsVisible();
         keyguardNotGoingAway();
         statusBarShadeIsNotLocked();
-        when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+        when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
+                true);
 
         // GIVEN active unlock triggers on biometric failures
         when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -2566,7 +2575,8 @@
         mKeyguardUpdateMonitor.mFaceAuthenticationCallback.onAuthenticationFailed();
 
         // THEN request unlock with NO keyguard dismissal
-        verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+        verify(mTrustManager).reportUserRequestedUnlock(
+                eq(mSelectedUserInteractor.getSelectedUserId()),
                 eq(false));
     }
 
@@ -2578,7 +2588,8 @@
         keyguardIsVisible();
         keyguardNotGoingAway();
         statusBarShadeIsNotLocked();
-        when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+        when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
+                true);
 
         // GIVEN active unlock triggers on biometric failures
         when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -2590,7 +2601,8 @@
         mKeyguardUpdateMonitor.mFaceAuthenticationCallback.onAuthenticationFailed();
 
         // THEN request unlock with a keyguard dismissal
-        verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+        verify(mTrustManager).reportUserRequestedUnlock(
+                eq(mSelectedUserInteractor.getSelectedUserId()),
                 eq(true));
     }
 
@@ -2600,7 +2612,8 @@
         // GIVEN shouldTriggerActiveUnlock
         when(mAuthController.isUdfpsFingerDown()).thenReturn(false);
         lockscreenBypassIsNotAllowed();
-        when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+        when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
+                true);
 
         // GIVEN active unlock triggers on biometric failures
         when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -2612,7 +2625,8 @@
         mKeyguardUpdateMonitor.mFaceAuthenticationCallback.onAuthenticationFailed();
 
         // THEN request unlock with a keyguard dismissal
-        verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+        verify(mTrustManager).reportUserRequestedUnlock(
+                eq(mSelectedUserInteractor.getSelectedUserId()),
                 eq(true));
     }
 
@@ -2701,7 +2715,8 @@
             throws RemoteException {
         // GIVEN shouldTriggerActiveUnlock
         keyguardIsVisible();
-        when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+        when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
+                true);
 
         // GIVEN active unlock triggers on wakeup
         when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -2717,7 +2732,8 @@
         mTestableLooper.processAllMessages();
 
         // THEN request unlock with a keyguard dismissal
-        verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+        verify(mTrustManager).reportUserRequestedUnlock(
+                eq(mSelectedUserInteractor.getSelectedUserId()),
                 eq(true));
     }
 
@@ -2726,7 +2742,8 @@
             throws RemoteException {
         // GIVEN shouldTriggerActiveUnlock on wake from UNFOLD_DEVICE
         keyguardIsVisible();
-        when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+        when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
+                true);
 
         // GIVEN active unlock triggers on wakeup
         when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -2742,7 +2759,8 @@
         mTestableLooper.processAllMessages();
 
         // THEN request unlock WITHOUT a keyguard dismissal
-        verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+        verify(mTrustManager).reportUserRequestedUnlock(
+                eq(mSelectedUserInteractor.getSelectedUserId()),
                 eq(false));
     }
 
@@ -2751,7 +2769,8 @@
             throws RemoteException {
         // GIVEN shouldTriggerActiveUnlock
         keyguardIsVisible();
-        when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+        when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
+                true);
 
         // GIVEN active unlock triggers on wakeup
         when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -2767,7 +2786,8 @@
         mTestableLooper.processAllMessages();
 
         // THEN request unlock with a keyguard dismissal
-        verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+        verify(mTrustManager).reportUserRequestedUnlock(
+                eq(mSelectedUserInteractor.getSelectedUserId()),
                 eq(true));
     }
 
@@ -2777,7 +2797,8 @@
             throws RemoteException {
         // GIVEN shouldTriggerActiveUnlock on wake from UNFOLD_DEVICE
         keyguardIsVisible();
-        when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+        when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
+                true);
 
         // GIVEN active unlock triggers on wakeup
         when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -2793,7 +2814,8 @@
         mTestableLooper.processAllMessages();
 
         // THEN request unlock WITHOUT a keyguard dismissal
-        verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+        verify(mTrustManager).reportUserRequestedUnlock(
+                eq(mSelectedUserInteractor.getSelectedUserId()),
                 eq(false));
     }
 
@@ -2857,27 +2879,29 @@
         assertThat(captor.getValue().getWakeReason())
                 .isEqualTo(PowerManager.WAKE_REASON_POWER_BUTTON);
     }
+
     @Test
     public void testFingerprintSensorProperties() throws RemoteException {
         mFingerprintAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(
                 new ArrayList<>());
 
         assertThat(mKeyguardUpdateMonitor.isUnlockWithFingerprintPossible(
-                KeyguardUpdateMonitor.getCurrentUser())).isFalse();
+                mSelectedUserInteractor.getSelectedUserId())).isFalse();
 
         mFingerprintAuthenticatorsRegisteredCallback
                 .onAllAuthenticatorsRegistered(mFingerprintSensorProperties);
 
         verifyFingerprintAuthenticateCall();
         assertThat(mKeyguardUpdateMonitor.isUnlockWithFingerprintPossible(
-                KeyguardUpdateMonitor.getCurrentUser())).isTrue();
+                mSelectedUserInteractor.getSelectedUserId())).isTrue();
     }
+
     @Test
     public void testFaceSensorProperties() throws RemoteException {
         mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(new ArrayList<>());
 
         assertThat(mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(
-                KeyguardUpdateMonitor.getCurrentUser())).isFalse();
+                mSelectedUserInteractor.getSelectedUserId())).isFalse();
 
         mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties);
         biometricsEnabledForCurrentUser();
@@ -2885,7 +2909,7 @@
         verifyFaceAuthenticateNeverCalled();
         verifyFaceDetectNeverCalled();
         assertThat(mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(
-                KeyguardUpdateMonitor.getCurrentUser())).isTrue();
+                mSelectedUserInteractor.getSelectedUserId())).isTrue();
     }
 
     @Test
@@ -2917,13 +2941,13 @@
         mKeyguardUpdateMonitor.onTrustChanged(
                 true /* enabled */,
                 true /* newlyUnlocked */,
-                getCurrentUser() /* userId */,
+                mSelectedUserInteractor.getSelectedUserId() /* userId */,
                 TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD /* flags */,
                 null /* trustGrantedMessages */);
 
         // THEN onTrustChanged is called FIRST
         final InOrder inOrder = Mockito.inOrder(callback);
-        inOrder.verify(callback).onTrustChanged(eq(getCurrentUser()));
+        inOrder.verify(callback).onTrustChanged(eq(mSelectedUserInteractor.getSelectedUserId()));
 
         // AND THEN onTrustGrantedForCurrentUser callback called
         inOrder.verify(callback).onTrustGrantedForCurrentUser(
@@ -3144,7 +3168,8 @@
 
     private void mockCanBypassLockscreen(boolean canBypass) {
         // force update the isFaceEnrolled cache:
-        mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(getCurrentUser());
+        mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(
+                mSelectedUserInteractor.getSelectedUserId());
 
         mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
         when(mKeyguardBypassController.canBypass()).thenReturn(canBypass);
@@ -3235,22 +3260,23 @@
 
     private void biometricsNotDisabledThroughDevicePolicyManager() {
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(null,
-                KeyguardUpdateMonitor.getCurrentUser())).thenReturn(0);
+                mSelectedUserInteractor.getSelectedUserId())).thenReturn(0);
     }
 
     private void biometricsEnabledForCurrentUser() throws RemoteException {
-        mBiometricEnabledOnKeyguardCallback.onChanged(true, KeyguardUpdateMonitor.getCurrentUser());
+        mBiometricEnabledOnKeyguardCallback.onChanged(true,
+                mSelectedUserInteractor.getSelectedUserId());
     }
 
     private void biometricsDisabledForCurrentUser() throws RemoteException {
         mBiometricEnabledOnKeyguardCallback.onChanged(
                 false,
-                KeyguardUpdateMonitor.getCurrentUser()
+                mSelectedUserInteractor.getSelectedUserId()
         );
     }
 
     private void primaryAuthRequiredEncrypted() {
-        when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
+        when(mStrongAuthTracker.getStrongAuthForUser(mSelectedUserInteractor.getSelectedUserId()))
                 .thenReturn(STRONG_AUTH_REQUIRED_AFTER_BOOT);
         when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
     }
@@ -3261,7 +3287,7 @@
     }
 
     private void primaryAuthNotRequiredByStrongAuthTracker() {
-        when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
+        when(mStrongAuthTracker.getStrongAuthForUser(mSelectedUserInteractor.getSelectedUserId()))
                 .thenReturn(0);
         when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
     }
@@ -3270,7 +3296,7 @@
         mKeyguardUpdateMonitor.onTrustChanged(
                 false,
                 false,
-                KeyguardUpdateMonitor.getCurrentUser(),
+                mSelectedUserInteractor.getSelectedUserId(),
                 -1,
                 new ArrayList<>()
         );
@@ -3435,7 +3461,7 @@
         protected TestableKeyguardUpdateMonitor(Context context) {
             super(context, mUserTracker,
                     TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper(),
-                    mBroadcastDispatcher, mSecureSettings, mDumpManager,
+                    mBroadcastDispatcher, mDumpManager,
                     mBackgroundExecutor, mMainExecutor,
                     mStatusBarStateController, mLockPatternUtils,
                     mAuthController, mTelephonyListenerManager,
@@ -3447,7 +3473,7 @@
                     mFaceWakeUpTriggersConfig, mDevicePostureController,
                     Optional.of(mInteractiveToAuthProvider),
                     mTaskStackChangeListeners, mActivityTaskManager, mDisplayTracker,
-                    mWakefulness);
+                    mWakefulness, mSelectedUserInteractor);
             setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
new file mode 100644
index 0000000..b1421b2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
@@ -0,0 +1,162 @@
+package com.android.systemui
+
+import android.graphics.Path
+import android.graphics.Rect
+import android.graphics.RectF
+import android.hardware.camera2.CameraManager
+import android.testing.AndroidTestingRunner
+import android.util.PathParser
+import androidx.test.filters.SmallTest
+import com.android.systemui.res.R
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.withArgCaptor
+import java.util.concurrent.Executor
+import kotlin.math.roundToInt
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class CameraAvailabilityListenerTest : SysuiTestCase() {
+    companion object {
+        const val EXCLUDED_PKG = "test.excluded.package"
+        const val CAMERA_ID_FRONT = "0"
+        const val CAMERA_ID_INNER = "1"
+        const val PROTECTION_PATH_STRING_FRONT = "M 50,50 a 20,20 0 1 0 40,0 a 20,20 0 1 0 -40,0 Z"
+        const val PROTECTION_PATH_STRING_INNER = "M 40,40 a 10,10 0 1 0 20,0 a 10,10 0 1 0 -20,0 Z"
+        val PATH_RECT_FRONT = rectFromPath(pathFromString(PROTECTION_PATH_STRING_FRONT))
+        val PATH_RECT_INNER = rectFromPath(pathFromString(PROTECTION_PATH_STRING_INNER))
+
+        private fun pathFromString(pathString: String): Path {
+            val spec = pathString.trim()
+            val p: Path
+            try {
+                p = PathParser.createPathFromPathData(spec)
+            } catch (e: Throwable) {
+                throw IllegalArgumentException("Invalid protection path", e)
+            }
+
+            return p
+        }
+
+        private fun rectFromPath(path: Path): Rect {
+            val computed = RectF()
+            path.computeBounds(computed)
+            return Rect(
+                computed.left.roundToInt(),
+                computed.top.roundToInt(),
+                computed.right.roundToInt(),
+                computed.bottom.roundToInt()
+            )
+        }
+    }
+
+    @Mock private lateinit var cameraManager: CameraManager
+    @Mock
+    private lateinit var cameraTransitionCb: CameraAvailabilityListener.CameraTransitionCallback
+    private lateinit var cameraAvailabilityListener: CameraAvailabilityListener
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        context
+            .getOrCreateTestableResources()
+            .addOverride(R.string.config_cameraProtectionExcludedPackages, EXCLUDED_PKG)
+        context
+            .getOrCreateTestableResources()
+            .addOverride(R.string.config_protectedCameraId, CAMERA_ID_FRONT)
+        context
+            .getOrCreateTestableResources()
+            .addOverride(
+                R.string.config_frontBuiltInDisplayCutoutProtection,
+                PROTECTION_PATH_STRING_FRONT
+            )
+        context
+            .getOrCreateTestableResources()
+            .addOverride(R.string.config_protectedInnerCameraId, CAMERA_ID_INNER)
+        context
+            .getOrCreateTestableResources()
+            .addOverride(
+                R.string.config_innerBuiltInDisplayCutoutProtection,
+                PROTECTION_PATH_STRING_INNER
+            )
+
+        context.addMockSystemService(CameraManager::class.java, cameraManager)
+
+        cameraAvailabilityListener =
+            CameraAvailabilityListener.Factory.build(context, context.mainExecutor)
+    }
+
+    @Test
+    fun testFrontCamera() {
+        var path: Path? = null
+        var rect: Rect? = null
+        val callback =
+            object : CameraAvailabilityListener.CameraTransitionCallback {
+                override fun onApplyCameraProtection(protectionPath: Path, bounds: Rect) {
+                    path = protectionPath
+                    rect = bounds
+                }
+
+                override fun onHideCameraProtection() {}
+            }
+
+        cameraAvailabilityListener.addTransitionCallback(callback)
+        cameraAvailabilityListener.startListening()
+
+        val callbackCaptor = withArgCaptor {
+            verify(cameraManager).registerAvailabilityCallback(any(Executor::class.java), capture())
+        }
+
+        callbackCaptor.onCameraOpened(CAMERA_ID_FRONT, "")
+        assertNotNull(path)
+        assertEquals(PATH_RECT_FRONT, rect)
+    }
+
+    @Test
+    fun testInnerCamera() {
+        var path: Path? = null
+        var rect: Rect? = null
+        val callback =
+            object : CameraAvailabilityListener.CameraTransitionCallback {
+                override fun onApplyCameraProtection(protectionPath: Path, bounds: Rect) {
+                    path = protectionPath
+                    rect = bounds
+                }
+
+                override fun onHideCameraProtection() {}
+            }
+
+        cameraAvailabilityListener.addTransitionCallback(callback)
+        cameraAvailabilityListener.startListening()
+
+        val callbackCaptor = withArgCaptor {
+            verify(cameraManager).registerAvailabilityCallback(any(Executor::class.java), capture())
+        }
+
+        callbackCaptor.onCameraOpened(CAMERA_ID_INNER, "")
+        assertNotNull(path)
+        assertEquals(PATH_RECT_INNER, rect)
+    }
+
+    @Test
+    fun testExcludedPackage() {
+        cameraAvailabilityListener.addTransitionCallback(cameraTransitionCb)
+        cameraAvailabilityListener.startListening()
+
+        val callbackCaptor = withArgCaptor {
+            verify(cameraManager).registerAvailabilityCallback(any(Executor::class.java), capture())
+        }
+        callbackCaptor.onCameraOpened(CAMERA_ID_FRONT, EXCLUDED_PKG)
+
+        verify(cameraTransitionCb, never())
+            .onApplyCameraProtection(any(Path::class.java), any(Rect::class.java))
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
index 5346db1..daa6070 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
@@ -19,8 +19,7 @@
 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
 
-import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
-import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -90,7 +89,7 @@
 
     @Before
     public void setUp() throws Exception {
-        setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
+        setFlagDefaults(mSetFlagsRule);
         MockitoAnnotations.initMocks(this);
         mContextWrapper = new ContextWrapper(mContext) {
             @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
index 3b2ea0f..5666435 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
@@ -16,8 +16,7 @@
 
 package com.android.systemui.accessibility.floatingmenu;
 
-import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
-import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults;
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
@@ -59,7 +58,7 @@
 
     @Before
     public void setUp() throws Exception {
-        setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
+        setFlagDefaults(mSetFlagsRule);
         final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
         final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
                 mock(SecureSettings.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
index 76a3153..ec6ec63 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
@@ -16,8 +16,7 @@
 
 package com.android.systemui.accessibility.floatingmenu;
 
-import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
-import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -78,7 +77,7 @@
 
     @Before
     public void setUp() throws Exception {
-        setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
+        setFlagDefaults(mSetFlagsRule);
         final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
         final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
                 stubWindowManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java
index 83bcd8d..e8192c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java
@@ -16,8 +16,7 @@
 
 package com.android.systemui.accessibility.floatingmenu;
 
-import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
-import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -29,8 +28,8 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -46,7 +45,7 @@
 
     @Before
     public void setUp() throws Exception {
-        setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
+        setFlagDefaults(mSetFlagsRule);
         final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
         mMenuViewAppearance = new MenuViewAppearance(mContext, windowManager);
         mMenuEduTooltipView = new MenuEduTooltipView(mContext, mMenuViewAppearance);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
index e01f1b7..62cb9a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
@@ -19,8 +19,7 @@
 import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS;
 import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
 
-import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
-import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -40,8 +39,8 @@
 import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
 import com.android.systemui.util.settings.SecureSettings;
 
 import org.junit.Before;
@@ -75,7 +74,7 @@
 
     @Before
     public void setUp() {
-        setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
+        setFlagDefaults(mSetFlagsRule);
         final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
         final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
                 stubWindowManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
index a88ee10..3248753 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
@@ -18,8 +18,7 @@
 
 import static android.view.View.OVER_SCROLL_NEVER;
 
-import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
-import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -83,7 +82,7 @@
 
     @Before
     public void setUp() throws Exception {
-        setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
+        setFlagDefaults(mSetFlagsRule);
         final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
         final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
                 mock(SecureSettings.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
index 41e5c20..03a4ba7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
@@ -19,8 +19,7 @@
 import static android.view.WindowInsets.Type.displayCutout;
 import static android.view.WindowInsets.Type.systemBars;
 
-import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
-import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doAnswer;
@@ -76,7 +75,7 @@
 
     @Before
     public void setUp() throws Exception {
-        setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
+        setFlagDefaults(mSetFlagsRule);
         final WindowManager wm = mContext.getSystemService(WindowManager.class);
         doAnswer(invocation -> wm.getMaximumWindowMetrics()).when(
                 mWindowManager).getMaximumWindowMetrics();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index b0776c9..df7d1b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -22,9 +22,8 @@
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.systemBars;
 
-import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
 import static com.android.systemui.accessibility.floatingmenu.MenuViewLayer.LayerIndex;
-import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -113,7 +112,7 @@
 
     @Before
     public void setUp() throws Exception {
-        setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
+        setFlagDefaults(mSetFlagsRule);
         final Rect mDisplayBounds = new Rect();
         mDisplayBounds.set(/* left= */ 0, /* top= */ 0, DISPLAY_WINDOW_WIDTH,
                 DISPLAY_WINDOW_HEIGHT);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
index ac2bfaf..b9fd5d0f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
@@ -18,8 +18,7 @@
 
 import static android.app.UiModeManager.MODE_NIGHT_YES;
 
-import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
-import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -70,7 +69,7 @@
 
     @Before
     public void setUp() throws Exception {
-        setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
+        setFlagDefaults(mSetFlagsRule);
         mUiModeManager = mContext.getSystemService(UiModeManager.class);
         mNightMode = mUiModeManager.getNightMode();
         mUiModeManager.setNightMode(MODE_NIGHT_YES);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/FlagUtils.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/FlagUtils.java
new file mode 100644
index 0000000..2975549
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/FlagUtils.java
@@ -0,0 +1,35 @@
+/*
+ * 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.accessibility.utils;
+
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import com.android.systemui.Flags;
+
+public class FlagUtils {
+    /**
+     * Populates a setFlagsRule with every SystemUI a11y feature flag.
+     * This function should be updated when new flags are added.
+     *
+     * @param setFlagsRule set flags rule from the test environment.
+     */
+    public static void setFlagDefaults(SetFlagsRule setFlagsRule) {
+        setFlagDefault(setFlagsRule, Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconControllerTest.kt
deleted file mode 100644
index 215d635..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconControllerTest.kt
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics
-
-import android.content.Context
-import android.hardware.biometrics.SensorProperties
-import android.hardware.fingerprint.FingerprintManager
-import android.hardware.fingerprint.FingerprintSensorProperties
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
-import android.view.ViewGroup.LayoutParams
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.res.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.`when` as whenEver
-import org.mockito.junit.MockitoJUnit
-
-private const val SENSOR_ID = 1
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class AuthBiometricFingerprintIconControllerTest : SysuiTestCase() {
-
-    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
-
-    @Mock private lateinit var iconView: LottieAnimationView
-    @Mock private lateinit var iconViewOverlay: LottieAnimationView
-    @Mock private lateinit var layoutParam: LayoutParams
-    @Mock private lateinit var fingerprintManager: FingerprintManager
-
-    private lateinit var controller: AuthBiometricFingerprintIconController
-
-    @Before
-    fun setUp() {
-        context.addMockSystemService(Context.FINGERPRINT_SERVICE, fingerprintManager)
-        whenEver(iconView.layoutParams).thenReturn(layoutParam)
-        whenEver(iconViewOverlay.layoutParams).thenReturn(layoutParam)
-    }
-
-    @Test
-    fun testIconContentDescription_SfpsDevice() {
-        setupFingerprintSensorProperties(FingerprintSensorProperties.TYPE_POWER_BUTTON)
-        controller = AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay)
-
-        assertThat(controller.getIconContentDescription(BiometricState.STATE_AUTHENTICATING))
-            .isEqualTo(
-                context.resources.getString(
-                    R.string.security_settings_sfps_enroll_find_sensor_message
-                )
-            )
-    }
-
-    @Test
-    fun testIconContentDescription_NonSfpsDevice() {
-        setupFingerprintSensorProperties(FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
-        controller = AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay)
-
-        assertThat(controller.getIconContentDescription(BiometricState.STATE_AUTHENTICATING))
-            .isEqualTo(context.resources.getString(R.string.fingerprint_dialog_touch_sensor))
-    }
-
-    private fun setupFingerprintSensorProperties(sensorType: Int) {
-        whenEver(fingerprintManager.sensorPropertiesInternal)
-            .thenReturn(
-                listOf(
-                    FingerprintSensorPropertiesInternal(
-                        SENSOR_ID,
-                        SensorProperties.STRENGTH_STRONG,
-                        5 /* maxEnrollmentsPerUser */,
-                        listOf() /* componentInfo */,
-                        sensorType,
-                        true /* halControlsIllumination */,
-                        true /* resetLockoutRequiresHardwareAuthToken */,
-                        listOf() /* sensorLocations */
-                    )
-                )
-            )
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
index d68a313..8c26776 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
@@ -72,6 +72,7 @@
             runCurrent()
 
             // WHEN shade expands
+            shadeRepository.setLegacyShadeTracking(true)
             shadeRepository.setLegacyShadeExpansion(.5f)
             runCurrent()
 
@@ -108,6 +109,7 @@
 
             // WHEN detector is disabled and shade opens
             detector.disable()
+            shadeRepository.setLegacyShadeTracking(true)
             shadeRepository.setLegacyShadeExpansion(.5f)
             runCurrent()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
index 9f24a9f..15633d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
@@ -30,6 +30,7 @@
 internal fun fingerprintSensorPropertiesInternal(
     ids: List<Int> = listOf(0),
     strong: Boolean = true,
+    sensorType: Int = FingerprintSensorProperties.TYPE_REAR
 ): List<FingerprintSensorPropertiesInternal> {
     val componentInfo =
         listOf(
@@ -54,7 +55,7 @@
             if (strong) SensorProperties.STRENGTH_STRONG else SensorProperties.STRENGTH_WEAK,
             5 /* maxEnrollmentsPerUser */,
             componentInfo,
-            FingerprintSensorProperties.TYPE_REAR,
+            sensorType,
             false /* resetLockoutRequiresHardwareAuthToken */
         )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index ebe13fe..c5f16aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -36,7 +36,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
@@ -46,12 +45,14 @@
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.settings.SecureSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -111,6 +112,7 @@
     @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
     @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
     @Mock private lateinit var udfpsUtils: UdfpsUtils
+    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
     @Mock private lateinit var udfpsKeyguardAccessibilityDelegate:
             UdfpsKeyguardAccessibilityDelegate
     @Mock private lateinit var udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels>
@@ -163,6 +165,7 @@
             isDebuggable,
             udfpsKeyguardAccessibilityDelegate,
             udfpsKeyguardViewModels,
+            mSelectedUserInteractor,
         )
         block()
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index dcb5398..f32e1a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -102,6 +102,7 @@
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.concurrency.FakeExecution;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
@@ -214,6 +215,8 @@
     private UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
     @Mock
     private Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels;
+    @Mock
+    private SelectedUserInteractor mSelectedUserInteractor;
 
     // Capture listeners so that they can be used to send events
     @Captor
@@ -326,7 +329,8 @@
                 mInputManager,
                 mock(KeyguardFaceAuthInteractor.class),
                 mUdfpsKeyguardAccessibilityDelegate,
-                mUdfpsKeyguardViewModels
+                mUdfpsKeyguardViewModels,
+                mSelectedUserInteractor
         );
         verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
         mOverlayController = mOverlayCaptor.getValue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
index e512adc..2c4e136 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
@@ -40,6 +40,7 @@
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import org.junit.Before;
@@ -69,6 +70,7 @@
     protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor;
     protected @Mock AlternateBouncerInteractor mAlternateBouncerInteractor;
     protected @Mock UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
+    protected @Mock SelectedUserInteractor mSelectedUserInteractor;
 
     protected FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
 
@@ -142,7 +144,8 @@
                 mFeatureFlags,
                 mPrimaryBouncerInteractor,
                 mAlternateBouncerInteractor,
-                mUdfpsKeyguardAccessibilityDelegate);
+                mUdfpsKeyguardAccessibilityDelegate,
+                mSelectedUserInteractor);
         return controller;
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
index 02ee53879..97dada2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
@@ -96,6 +96,7 @@
                 mKeyguardUpdateMonitor,
                 FakeTrustRepository(),
                 testScope.backgroundScope,
+                mSelectedUserInteractor,
             )
         mAlternateBouncerInteractor =
             AlternateBouncerInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
index 6b9c34b..bbf471c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
@@ -55,6 +56,7 @@
 
     @Mock private lateinit var udfpsOverlayParams: UdfpsOverlayParams
     @Mock private lateinit var overlayBounds: Rect
+    @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
 
     private lateinit var underTest: UdfpsOverlayInteractor
 
@@ -104,7 +106,12 @@
         }
 
     private fun createUdpfsOverlayInteractor() {
-        underTest = UdfpsOverlayInteractor(authController, testScope.backgroundScope)
+        underTest =
+            UdfpsOverlayInteractor(
+                authController,
+                selectedUserInteractor,
+                testScope.backgroundScope
+            )
         testScope.runCurrent()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt
deleted file mode 100644
index fd86486..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-package com.android.systemui.biometrics.ui.viewmodel
-
-import android.content.res.Configuration
-import androidx.test.filters.SmallTest
-import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
-import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
-import com.android.systemui.biometrics.data.repository.FakePromptRepository
-import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
-import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
-import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
-import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl
-import com.android.systemui.biometrics.shared.model.FingerprintSensorType
-import com.android.systemui.biometrics.shared.model.SensorStrength
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.display.data.repository.FakeDisplayRepository
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mock
-import org.mockito.junit.MockitoJUnit
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(JUnit4::class)
-class PromptFingerprintIconViewModelTest : SysuiTestCase() {
-
-    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
-
-    @Mock private lateinit var lockPatternUtils: LockPatternUtils
-
-    private lateinit var displayRepository: FakeDisplayRepository
-    private lateinit var fingerprintRepository: FakeFingerprintPropertyRepository
-    private lateinit var promptRepository: FakePromptRepository
-    private lateinit var displayStateRepository: FakeDisplayStateRepository
-
-    private val testScope = TestScope(StandardTestDispatcher())
-    private val fakeExecutor = FakeExecutor(FakeSystemClock())
-
-    private lateinit var promptSelectorInteractor: PromptSelectorInteractor
-    private lateinit var displayStateInteractor: DisplayStateInteractor
-    private lateinit var viewModel: PromptFingerprintIconViewModel
-
-    @Before
-    fun setup() {
-        displayRepository = FakeDisplayRepository()
-        fingerprintRepository = FakeFingerprintPropertyRepository()
-        promptRepository = FakePromptRepository()
-        displayStateRepository = FakeDisplayStateRepository()
-
-        promptSelectorInteractor =
-            PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils)
-        displayStateInteractor =
-            DisplayStateInteractorImpl(
-                testScope.backgroundScope,
-                mContext,
-                fakeExecutor,
-                displayStateRepository,
-                displayRepository,
-            )
-        viewModel = PromptFingerprintIconViewModel(displayStateInteractor, promptSelectorInteractor)
-    }
-
-    @Test
-    fun sfpsIconUpdates_onConfigurationChanged() {
-        testScope.runTest {
-            runCurrent()
-            configureFingerprintPropertyRepository(FingerprintSensorType.POWER_BUTTON)
-            val testConfig = Configuration()
-            val folded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP - 1
-            val unfolded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP + 1
-            val currentIcon = collectLastValue(viewModel.iconAsset)
-
-            testConfig.smallestScreenWidthDp = folded
-            viewModel.onConfigurationChanged(testConfig)
-            val foldedIcon = currentIcon()
-
-            testConfig.smallestScreenWidthDp = unfolded
-            viewModel.onConfigurationChanged(testConfig)
-            val unfoldedIcon = currentIcon()
-
-            assertThat(foldedIcon).isNotEqualTo(unfoldedIcon)
-        }
-    }
-
-    private fun configureFingerprintPropertyRepository(sensorType: FingerprintSensorType) {
-        fingerprintRepository.setProperties(0, SensorStrength.STRONG, sensorType, mapOf())
-    }
-}
-
-internal const val INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP = 600
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index ca6df40..b695a0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -16,8 +16,10 @@
 
 package com.android.systemui.biometrics.ui.viewmodel
 
+import android.content.res.Configuration
 import android.hardware.biometrics.PromptInfo
 import android.hardware.face.FaceSensorPropertiesInternal
+import android.hardware.fingerprint.FingerprintSensorProperties
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
 import android.view.HapticFeedbackConstants
 import android.view.MotionEvent
@@ -36,12 +38,15 @@
 import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal
 import com.android.systemui.biometrics.shared.model.BiometricModalities
 import com.android.systemui.biometrics.shared.model.BiometricModality
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
+import com.android.systemui.biometrics.shared.model.DisplayRotation
+import com.android.systemui.biometrics.shared.model.toSensorStrength
+import com.android.systemui.biometrics.shared.model.toSensorType
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.display.data.repository.FakeDisplayRepository
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
@@ -66,6 +71,7 @@
 
 private const val USER_ID = 4
 private const val CHALLENGE = 2L
+private const val DELAY = 1000L
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -88,11 +94,22 @@
 
     private lateinit var selector: PromptSelectorInteractor
     private lateinit var viewModel: PromptViewModel
+    private lateinit var iconViewModel: PromptIconViewModel
     private val featureFlags = FakeFeatureFlags()
 
     @Before
     fun setup() {
         fingerprintRepository = FakeFingerprintPropertyRepository()
+        testCase.fingerprint?.let {
+            fingerprintRepository.setProperties(
+                it.sensorId,
+                it.sensorStrength.toSensorStrength(),
+                it.sensorType.toSensorType(),
+                it.allLocations.associateBy { sensorLocationInternal ->
+                    sensorLocationInternal.displayId
+                }
+            )
+        }
         promptRepository = FakePromptRepository()
         displayStateRepository = FakeDisplayStateRepository()
         displayRepository = FakeDisplayRepository()
@@ -110,6 +127,7 @@
 
         viewModel =
             PromptViewModel(displayStateInteractor, selector, vibrator, mContext, featureFlags)
+        iconViewModel = viewModel.iconViewModel
         featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
     }
 
@@ -123,7 +141,6 @@
             val modalities by collectLastValue(viewModel.modalities)
             val message by collectLastValue(viewModel.message)
             val size by collectLastValue(viewModel.size)
-            val legacyState by collectLastValue(viewModel.legacyState)
 
             assertThat(authenticating).isFalse()
             assertThat(authenticated?.isNotAuthenticated).isTrue()
@@ -133,7 +150,6 @@
             }
             assertThat(message).isEqualTo(PromptMessage.Empty)
             assertThat(size).isEqualTo(expectedSize)
-            assertThat(legacyState).isEqualTo(BiometricState.STATE_IDLE)
 
             val startMessage = "here we go"
             viewModel.showAuthenticating(startMessage, isRetry = false)
@@ -143,7 +159,6 @@
             assertThat(authenticated?.isNotAuthenticated).isTrue()
             assertThat(size).isEqualTo(expectedSize)
             assertButtonsVisible(negative = expectedSize != PromptSize.SMALL)
-            assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATING)
         }
 
     @Test
@@ -205,6 +220,472 @@
         assertThat(currentConstant).isEqualTo(HapticFeedbackConstants.REJECT)
     }
 
+    @Test
+    fun start_idle_and_show_authenticating_iconUpdate() =
+        runGenericTest(doNotStart = true) {
+            val currentRotation by collectLastValue(displayStateInteractor.currentRotation)
+            val iconAsset by collectLastValue(iconViewModel.iconAsset)
+            val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId)
+            val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView)
+
+            val forceExplicitFlow = testCase.isCoex && testCase.authenticatedByFingerprint
+            if (forceExplicitFlow) {
+                viewModel.ensureFingerprintHasStarted(isDelayed = true)
+            }
+
+            val startMessage = "here we go"
+            viewModel.showAuthenticating(startMessage, isRetry = false)
+
+            if (testCase.isFingerprintOnly) {
+                val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+                val shouldAnimateIconOverlay by
+                    collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+                if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+                    val expectedOverlayAsset =
+                        when (currentRotation) {
+                            DisplayRotation.ROTATION_0 ->
+                                R.raw.biometricprompt_fingerprint_to_error_landscape
+                            DisplayRotation.ROTATION_90 ->
+                                R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
+                            DisplayRotation.ROTATION_180 ->
+                                R.raw.biometricprompt_fingerprint_to_error_landscape
+                            DisplayRotation.ROTATION_270 ->
+                                R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
+                            else -> throw Exception("invalid rotation")
+                        }
+                    assertThat(iconOverlayAsset).isEqualTo(expectedOverlayAsset)
+                    assertThat(iconContentDescriptionId)
+                        .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message)
+                    assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+                } else {
+                    assertThat(iconAsset)
+                        .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie)
+                    assertThat(iconOverlayAsset).isEqualTo(-1)
+                    assertThat(iconContentDescriptionId)
+                        .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+                    assertThat(shouldAnimateIconView).isEqualTo(false)
+                    assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+                }
+            }
+
+            if (testCase.isFaceOnly) {
+                val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+                val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+                val lastPulseLightToDark by collectLastValue(iconViewModel.lastPulseLightToDark)
+
+                val expectedIconAsset =
+                    if (shouldPulseAnimation!!) {
+                        if (lastPulseLightToDark!!) {
+                            R.drawable.face_dialog_pulse_dark_to_light
+                        } else {
+                            R.drawable.face_dialog_pulse_light_to_dark
+                        }
+                    } else {
+                        R.drawable.face_dialog_pulse_dark_to_light
+                    }
+                assertThat(iconAsset).isEqualTo(expectedIconAsset)
+                assertThat(iconContentDescriptionId)
+                    .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticating)
+                assertThat(shouldAnimateIconView).isEqualTo(true)
+                assertThat(shouldRepeatAnimation).isEqualTo(true)
+            }
+
+            if (testCase.isCoex) {
+                if (testCase.confirmationRequested || forceExplicitFlow) {
+                    // explicit flow
+                    val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+                    val shouldAnimateIconOverlay by
+                        collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+                    // TODO: Update when SFPS co-ex is implemented
+                    if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+                        assertThat(iconAsset)
+                            .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie)
+                        assertThat(iconOverlayAsset).isEqualTo(-1)
+                        assertThat(iconContentDescriptionId)
+                            .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+                        assertThat(shouldAnimateIconView).isEqualTo(false)
+                        assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+                    }
+                } else {
+                    // implicit flow
+                    val shouldRepeatAnimation by
+                        collectLastValue(iconViewModel.shouldRepeatAnimation)
+                    val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+                    val lastPulseLightToDark by collectLastValue(iconViewModel.lastPulseLightToDark)
+
+                    val expectedIconAsset =
+                        if (shouldPulseAnimation!!) {
+                            if (lastPulseLightToDark!!) {
+                                R.drawable.face_dialog_pulse_dark_to_light
+                            } else {
+                                R.drawable.face_dialog_pulse_light_to_dark
+                            }
+                        } else {
+                            R.drawable.face_dialog_pulse_dark_to_light
+                        }
+                    assertThat(iconAsset).isEqualTo(expectedIconAsset)
+                    assertThat(iconContentDescriptionId)
+                        .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticating)
+                    assertThat(shouldAnimateIconView).isEqualTo(true)
+                    assertThat(shouldRepeatAnimation).isEqualTo(true)
+                }
+            }
+        }
+
+    @Test
+    fun start_authenticating_show_and_clear_error_iconUpdate() = runGenericTest {
+        val currentRotation by collectLastValue(displayStateInteractor.currentRotation)
+
+        val iconAsset by collectLastValue(iconViewModel.iconAsset)
+        val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId)
+        val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView)
+
+        val forceExplicitFlow = testCase.isCoex && testCase.authenticatedByFingerprint
+        if (forceExplicitFlow) {
+            viewModel.ensureFingerprintHasStarted(isDelayed = true)
+        }
+
+        val errorJob = launch {
+            viewModel.showTemporaryError(
+                "so sad",
+                messageAfterError = "",
+                authenticateAfterError = testCase.isFingerprintOnly || testCase.isCoex,
+            )
+            // Usually done by binder
+            iconViewModel.setPreviousIconWasError(true)
+            iconViewModel.setPreviousIconOverlayWasError(true)
+        }
+
+        if (testCase.isFingerprintOnly) {
+            val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+            val shouldAnimateIconOverlay by collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+            if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+                val expectedOverlayAsset =
+                    when (currentRotation) {
+                        DisplayRotation.ROTATION_0 ->
+                            R.raw.biometricprompt_fingerprint_to_error_landscape
+                        DisplayRotation.ROTATION_90 ->
+                            R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
+                        DisplayRotation.ROTATION_180 ->
+                            R.raw.biometricprompt_fingerprint_to_error_landscape
+                        DisplayRotation.ROTATION_270 ->
+                            R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
+                        else -> throw Exception("invalid rotation")
+                    }
+                assertThat(iconOverlayAsset).isEqualTo(expectedOverlayAsset)
+                assertThat(iconContentDescriptionId).isEqualTo(R.string.biometric_dialog_try_again)
+                assertThat(shouldAnimateIconOverlay).isEqualTo(true)
+            } else {
+                assertThat(iconAsset)
+                    .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie)
+                assertThat(iconOverlayAsset).isEqualTo(-1)
+                assertThat(iconContentDescriptionId).isEqualTo(R.string.biometric_dialog_try_again)
+                assertThat(shouldAnimateIconView).isEqualTo(true)
+                assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+            }
+
+            // Clear error, restart authenticating
+            errorJob.join()
+
+            if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+                val expectedOverlayAsset =
+                    when (currentRotation) {
+                        DisplayRotation.ROTATION_0 ->
+                            R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
+                        DisplayRotation.ROTATION_90 ->
+                            R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft
+                        DisplayRotation.ROTATION_180 ->
+                            R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
+                        DisplayRotation.ROTATION_270 ->
+                            R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright
+                        else -> throw Exception("invalid rotation")
+                    }
+                assertThat(iconOverlayAsset).isEqualTo(expectedOverlayAsset)
+                assertThat(iconContentDescriptionId)
+                    .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message)
+                assertThat(shouldAnimateIconOverlay).isEqualTo(true)
+            } else {
+                assertThat(iconAsset)
+                    .isEqualTo(R.raw.fingerprint_dialogue_error_to_fingerprint_lottie)
+                assertThat(iconOverlayAsset).isEqualTo(-1)
+                assertThat(iconContentDescriptionId)
+                    .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+                assertThat(shouldAnimateIconView).isEqualTo(true)
+                assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+            }
+        }
+
+        if (testCase.isFaceOnly) {
+            val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+            val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+
+            assertThat(shouldPulseAnimation!!).isEqualTo(false)
+            assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_dark_to_error)
+            assertThat(iconContentDescriptionId).isEqualTo(R.string.keyguard_face_failed)
+            assertThat(shouldAnimateIconView).isEqualTo(true)
+            assertThat(shouldRepeatAnimation).isEqualTo(false)
+
+            // Clear error, go to idle
+            errorJob.join()
+
+            assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_error_to_idle)
+            assertThat(iconContentDescriptionId)
+                .isEqualTo(R.string.biometric_dialog_face_icon_description_idle)
+            assertThat(shouldAnimateIconView).isEqualTo(true)
+            assertThat(shouldRepeatAnimation).isEqualTo(false)
+        }
+
+        if (testCase.isCoex) {
+            val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+            val shouldAnimateIconOverlay by collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+            // TODO: Update when SFPS co-ex is implemented
+            if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+                assertThat(iconAsset)
+                    .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie)
+                assertThat(iconOverlayAsset).isEqualTo(-1)
+                assertThat(iconContentDescriptionId).isEqualTo(R.string.biometric_dialog_try_again)
+                assertThat(shouldAnimateIconView).isEqualTo(true)
+                assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+            }
+
+            // Clear error, restart authenticating
+            errorJob.join()
+
+            if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+                assertThat(iconAsset)
+                    .isEqualTo(R.raw.fingerprint_dialogue_error_to_fingerprint_lottie)
+                assertThat(iconOverlayAsset).isEqualTo(-1)
+                assertThat(iconContentDescriptionId)
+                    .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+                assertThat(shouldAnimateIconView).isEqualTo(true)
+                assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+            }
+        }
+    }
+
+    @Test
+    fun shows_authenticated_no_errors_no_confirmation_required_iconUpdate() = runGenericTest {
+        if (!testCase.confirmationRequested) {
+            val currentRotation by collectLastValue(displayStateInteractor.currentRotation)
+
+            val iconAsset by collectLastValue(iconViewModel.iconAsset)
+            val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId)
+            val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView)
+
+            viewModel.showAuthenticated(
+                modality = testCase.authenticatedModality,
+                dismissAfterDelay = DELAY
+            )
+
+            if (testCase.isFingerprintOnly) {
+                val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+                val shouldAnimateIconOverlay by
+                    collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+                if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+                    val expectedOverlayAsset =
+                        when (currentRotation) {
+                            DisplayRotation.ROTATION_0 ->
+                                R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
+                            DisplayRotation.ROTATION_90 ->
+                                R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
+                            DisplayRotation.ROTATION_180 ->
+                                R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
+                            DisplayRotation.ROTATION_270 ->
+                                R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright
+                            else -> throw Exception("invalid rotation")
+                        }
+                    assertThat(iconOverlayAsset).isEqualTo(expectedOverlayAsset)
+                    assertThat(iconContentDescriptionId)
+                        .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message)
+                    assertThat(shouldAnimateIconOverlay).isEqualTo(true)
+                } else {
+                    val isAuthenticated by collectLastValue(viewModel.isAuthenticated)
+                    assertThat(iconAsset)
+                        .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_success_lottie)
+                    assertThat(iconOverlayAsset).isEqualTo(-1)
+                    assertThat(iconContentDescriptionId)
+                        .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+                    assertThat(shouldAnimateIconView).isEqualTo(true)
+                    assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+                }
+            }
+
+            // If co-ex, using implicit flow (explicit flow always requires confirmation)
+            if (testCase.isFaceOnly || testCase.isCoex) {
+                val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+                val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+
+                assertThat(shouldPulseAnimation!!).isEqualTo(false)
+                assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_dark_to_checkmark)
+                assertThat(iconContentDescriptionId)
+                    .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated)
+                assertThat(shouldAnimateIconView).isEqualTo(true)
+                assertThat(shouldRepeatAnimation).isEqualTo(false)
+            }
+        }
+    }
+
+    @Test
+    fun shows_pending_confirmation_iconUpdate() = runGenericTest {
+        if (
+            (testCase.isFaceOnly || testCase.isCoex) &&
+                testCase.authenticatedByFace &&
+                testCase.confirmationRequested
+        ) {
+            val iconAsset by collectLastValue(iconViewModel.iconAsset)
+            val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId)
+            val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView)
+
+            viewModel.showAuthenticated(
+                modality = testCase.authenticatedModality,
+                dismissAfterDelay = DELAY
+            )
+
+            if (testCase.isFaceOnly) {
+                val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+                val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+
+                assertThat(shouldPulseAnimation!!).isEqualTo(false)
+                assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_wink_from_dark)
+                assertThat(iconContentDescriptionId)
+                    .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated)
+                assertThat(shouldAnimateIconView).isEqualTo(true)
+                assertThat(shouldRepeatAnimation).isEqualTo(false)
+            }
+
+            // explicit flow because confirmation requested
+            if (testCase.isCoex) {
+                val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+                val shouldAnimateIconOverlay by
+                    collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+                // TODO: Update when SFPS co-ex is implemented
+                if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+                    assertThat(iconAsset)
+                        .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie)
+                    assertThat(iconOverlayAsset).isEqualTo(-1)
+                    assertThat(iconContentDescriptionId)
+                        .isEqualTo(R.string.fingerprint_dialog_authenticated_confirmation)
+                    assertThat(shouldAnimateIconView).isEqualTo(true)
+                    assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun shows_authenticated_explicitly_confirmed_iconUpdate() = runGenericTest {
+        if (
+            (testCase.isFaceOnly || testCase.isCoex) &&
+                testCase.authenticatedByFace &&
+                testCase.confirmationRequested
+        ) {
+            val iconAsset by collectLastValue(iconViewModel.iconAsset)
+            val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId)
+            val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView)
+
+            viewModel.showAuthenticated(
+                modality = testCase.authenticatedModality,
+                dismissAfterDelay = DELAY
+            )
+
+            viewModel.confirmAuthenticated()
+
+            if (testCase.isFaceOnly) {
+                val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+                val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+
+                assertThat(shouldPulseAnimation!!).isEqualTo(false)
+                assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_dark_to_checkmark)
+                assertThat(iconContentDescriptionId)
+                    .isEqualTo(R.string.biometric_dialog_face_icon_description_confirmed)
+                assertThat(shouldAnimateIconView).isEqualTo(true)
+                assertThat(shouldRepeatAnimation).isEqualTo(false)
+            }
+
+            // explicit flow because confirmation requested
+            if (testCase.isCoex) {
+                val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+                val shouldAnimateIconOverlay by
+                    collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+                // TODO: Update when SFPS co-ex is implemented
+                if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+                    assertThat(iconAsset)
+                        .isEqualTo(R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie)
+                    assertThat(iconOverlayAsset).isEqualTo(-1)
+                    assertThat(iconContentDescriptionId)
+                        .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+                    assertThat(shouldAnimateIconView).isEqualTo(true)
+                    assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun sfpsIconUpdates_onConfigurationChanged() = runGenericTest {
+        if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+            val testConfig = Configuration()
+            val folded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP - 1
+            val unfolded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP + 1
+            val currentIcon by collectLastValue(iconViewModel.iconAsset)
+
+            testConfig.smallestScreenWidthDp = folded
+            iconViewModel.onConfigurationChanged(testConfig)
+            val foldedIcon = currentIcon
+
+            testConfig.smallestScreenWidthDp = unfolded
+            iconViewModel.onConfigurationChanged(testConfig)
+            val unfoldedIcon = currentIcon
+
+            assertThat(foldedIcon).isNotEqualTo(unfoldedIcon)
+        }
+    }
+
+    @Test
+    fun sfpsIconUpdates_onRotation() = runGenericTest {
+        if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+            val currentIcon by collectLastValue(iconViewModel.iconAsset)
+
+            displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_0)
+            val iconRotation0 = currentIcon
+
+            displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90)
+            val iconRotation90 = currentIcon
+
+            displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180)
+            val iconRotation180 = currentIcon
+
+            displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270)
+            val iconRotation270 = currentIcon
+
+            assertThat(iconRotation0).isEqualTo(iconRotation180)
+            assertThat(iconRotation0).isNotEqualTo(iconRotation90)
+            assertThat(iconRotation0).isNotEqualTo(iconRotation270)
+        }
+    }
+
+    @Test
+    fun sfpsIconUpdates_onRearDisplayMode() = runGenericTest {
+        if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+            val currentIcon by collectLastValue(iconViewModel.iconAsset)
+
+            displayStateRepository.setIsInRearDisplayMode(false)
+            val iconNotRearDisplayMode = currentIcon
+
+            displayStateRepository.setIsInRearDisplayMode(true)
+            val iconRearDisplayMode = currentIcon
+
+            assertThat(iconNotRearDisplayMode).isNotEqualTo(iconRearDisplayMode)
+        }
+    }
+
     private suspend fun TestScope.showAuthenticated(
         authenticatedModality: BiometricModality,
         expectConfirmation: Boolean,
@@ -213,7 +694,6 @@
         val authenticated by collectLastValue(viewModel.isAuthenticated)
         val fpStartMode by collectLastValue(viewModel.fingerprintStartMode)
         val size by collectLastValue(viewModel.size)
-        val legacyState by collectLastValue(viewModel.legacyState)
 
         val authWithSmallPrompt =
             testCase.shouldStartAsImplicitFlow &&
@@ -221,14 +701,12 @@
         assertThat(authenticating).isTrue()
         assertThat(authenticated?.isNotAuthenticated).isTrue()
         assertThat(size).isEqualTo(if (authWithSmallPrompt) PromptSize.SMALL else PromptSize.MEDIUM)
-        assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATING)
         assertButtonsVisible(negative = !authWithSmallPrompt)
 
-        val delay = 1000L
-        viewModel.showAuthenticated(authenticatedModality, delay)
+        viewModel.showAuthenticated(authenticatedModality, DELAY)
 
         assertThat(authenticated?.isAuthenticated).isTrue()
-        assertThat(authenticated?.delay).isEqualTo(delay)
+        assertThat(authenticated?.delay).isEqualTo(DELAY)
         assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation)
         assertThat(size)
             .isEqualTo(
@@ -238,14 +716,7 @@
                     PromptSize.SMALL
                 }
             )
-        assertThat(legacyState)
-            .isEqualTo(
-                if (expectConfirmation) {
-                    BiometricState.STATE_PENDING_CONFIRMATION
-                } else {
-                    BiometricState.STATE_AUTHENTICATED
-                }
-            )
+
         assertButtonsVisible(
             cancel = expectConfirmation,
             confirm = expectConfirmation,
@@ -298,7 +769,6 @@
         val message by collectLastValue(viewModel.message)
         val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
         val size by collectLastValue(viewModel.size)
-        val legacyState by collectLastValue(viewModel.legacyState)
         val canTryAgainNow by collectLastValue(viewModel.canTryAgainNow)
 
         val errorJob = launch {
@@ -312,7 +782,6 @@
         assertThat(size).isEqualTo(PromptSize.MEDIUM)
         assertThat(message).isEqualTo(PromptMessage.Error(errorMessage))
         assertThat(messageVisible).isTrue()
-        assertThat(legacyState).isEqualTo(BiometricState.STATE_ERROR)
 
         // temporary error should disappear after a delay
         errorJob.join()
@@ -323,17 +792,6 @@
             assertThat(message).isEqualTo(PromptMessage.Empty)
             assertThat(messageVisible).isFalse()
         }
-        val clearIconError = !restart
-        assertThat(legacyState)
-            .isEqualTo(
-                if (restart) {
-                    BiometricState.STATE_AUTHENTICATING
-                } else if (clearIconError) {
-                    BiometricState.STATE_IDLE
-                } else {
-                    BiometricState.STATE_HELP
-                }
-            )
 
         assertThat(authenticating).isEqualTo(restart)
         assertThat(authenticated?.isNotAuthenticated).isTrue()
@@ -488,7 +946,6 @@
         val authenticated by collectLastValue(viewModel.isAuthenticated)
         val message by collectLastValue(viewModel.message)
         val size by collectLastValue(viewModel.size)
-        val legacyState by collectLastValue(viewModel.legacyState)
         val canTryAgain by collectLastValue(viewModel.canTryAgainNow)
 
         assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation)
@@ -506,7 +963,6 @@
 
         assertThat(authenticating).isFalse()
         assertThat(authenticated?.isAuthenticated).isTrue()
-        assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED)
         assertThat(canTryAgain).isFalse()
     }
 
@@ -524,7 +980,6 @@
         val authenticated by collectLastValue(viewModel.isAuthenticated)
         val message by collectLastValue(viewModel.message)
         val size by collectLastValue(viewModel.size)
-        val legacyState by collectLastValue(viewModel.legacyState)
         val canTryAgain by collectLastValue(viewModel.canTryAgainNow)
 
         assertThat(authenticating).isFalse()
@@ -532,8 +987,6 @@
         assertThat(authenticated?.isAuthenticated).isTrue()
 
         if (testCase.isFaceOnly && expectConfirmation) {
-            assertThat(legacyState).isEqualTo(BiometricState.STATE_PENDING_CONFIRMATION)
-
             assertThat(size).isEqualTo(PromptSize.MEDIUM)
             assertButtonsVisible(
                 cancel = true,
@@ -543,8 +996,6 @@
             viewModel.confirmAuthenticated()
             assertThat(message).isEqualTo(PromptMessage.Empty)
             assertButtonsVisible()
-        } else {
-            assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED)
         }
     }
 
@@ -563,7 +1014,6 @@
         val authenticated by collectLastValue(viewModel.isAuthenticated)
         val message by collectLastValue(viewModel.message)
         val size by collectLastValue(viewModel.size)
-        val legacyState by collectLastValue(viewModel.legacyState)
         val canTryAgain by collectLastValue(viewModel.canTryAgainNow)
 
         assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation)
@@ -581,7 +1031,6 @@
 
         assertThat(authenticating).isFalse()
         assertThat(authenticated?.isAuthenticated).isTrue()
-        assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED)
         assertThat(canTryAgain).isFalse()
     }
 
@@ -610,12 +1059,10 @@
         val message by collectLastValue(viewModel.message)
         val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
         val size by collectLastValue(viewModel.size)
-        val legacyState by collectLastValue(viewModel.legacyState)
 
         viewModel.showHelp(helpMessage)
 
         assertThat(size).isEqualTo(PromptSize.MEDIUM)
-        assertThat(legacyState).isEqualTo(BiometricState.STATE_HELP)
         assertThat(message).isEqualTo(PromptMessage.Help(helpMessage))
         assertThat(messageVisible).isTrue()
 
@@ -632,7 +1079,6 @@
         val message by collectLastValue(viewModel.message)
         val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
         val size by collectLastValue(viewModel.size)
-        val legacyState by collectLastValue(viewModel.legacyState)
         val confirmationRequired by collectLastValue(viewModel.isConfirmationRequired)
 
         if (testCase.isCoex && testCase.authenticatedByFingerprint) {
@@ -642,11 +1088,7 @@
         viewModel.showHelp(helpMessage)
 
         assertThat(size).isEqualTo(PromptSize.MEDIUM)
-        if (confirmationRequired == true) {
-            assertThat(legacyState).isEqualTo(BiometricState.STATE_PENDING_CONFIRMATION)
-        } else {
-            assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED)
-        }
+
         assertThat(message).isEqualTo(PromptMessage.Help(helpMessage))
         assertThat(messageVisible).isTrue()
         assertThat(authenticating).isFalse()
@@ -785,6 +1227,15 @@
                     authenticatedModality = BiometricModality.Fingerprint,
                 ),
                 TestCase(
+                    fingerprint =
+                        fingerprintSensorPropertiesInternal(
+                                strong = true,
+                                sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON
+                            )
+                            .first(),
+                    authenticatedModality = BiometricModality.Fingerprint,
+                ),
+                TestCase(
                     face = faceSensorPropertiesInternal(strong = true).first(),
                     authenticatedModality = BiometricModality.Face,
                     confirmationRequested = true,
@@ -794,6 +1245,16 @@
                     authenticatedModality = BiometricModality.Fingerprint,
                     confirmationRequested = true,
                 ),
+                TestCase(
+                    fingerprint =
+                        fingerprintSensorPropertiesInternal(
+                                strong = true,
+                                sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON
+                            )
+                            .first(),
+                    authenticatedModality = BiometricModality.Fingerprint,
+                    confirmationRequested = true,
+                ),
             )
 
         private val coexTestCases =
@@ -834,7 +1295,9 @@
         val modality =
             when {
                 fingerprint != null && face != null -> "coex"
-                fingerprint != null -> "fingerprint only"
+                fingerprint != null && fingerprint.isAnySidefpsType -> "fingerprint only, sideFps"
+                fingerprint != null && !fingerprint.isAnySidefpsType ->
+                    "fingerprint only, non-sideFps"
                 face != null -> "face only"
                 else -> "?"
             }
@@ -864,6 +1327,8 @@
     val isCoex: Boolean
         get() = face != null && fingerprint != null
 
+    @FingerprintSensorProperties.SensorType val sensorType: Int? = fingerprint?.sensorType
+
     val shouldStartAsImplicitFlow: Boolean
         get() = (isFaceOnly || isCoex) && !confirmationRequested
 }
@@ -890,3 +1355,5 @@
         BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face),
     )
 }
+
+internal const val INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP = 600
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
index cc4eca5..b48bc1d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
@@ -48,6 +48,7 @@
 import com.android.systemui.res.R.string.kg_trust_agent_disabled
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.KotlinArgumentCaptor
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
@@ -86,6 +87,7 @@
     @Mock private lateinit var countDownTimerUtil: CountDownTimerUtil
     @Mock private lateinit var systemPropertiesHelper: SystemPropertiesHelper
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
 
     private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
     private lateinit var testScope: TestScope
@@ -119,6 +121,7 @@
                 keyguardUpdateMonitor,
                 fakeTrustRepository,
                 testScope.backgroundScope,
+                mSelectedUserInteractor,
             )
         underTest =
             BouncerMessageInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
index 9a5b4585..f6b284f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -25,7 +25,6 @@
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.DejankUtils
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
@@ -37,7 +36,9 @@
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.FakeTrustRepository
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.utils.os.FakeHandler
@@ -70,6 +71,7 @@
     @Mock private lateinit var falsingCollector: FalsingCollector
     @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
     private lateinit var mainHandler: FakeHandler
     private lateinit var underTest: PrimaryBouncerInteractor
     private lateinit var resources: TestableResources
@@ -100,6 +102,7 @@
                 keyguardUpdateMonitor,
                 trustRepository,
                 testScope.backgroundScope,
+                mSelectedUserInteractor,
             )
         whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
         whenever(repository.primaryBouncerShow.value).thenReturn(false)
@@ -108,6 +111,27 @@
     }
 
     @Test
+    fun show_nullDelegate() {
+        testScope.run {
+            whenever(bouncerView.delegate).thenReturn(null)
+            mainHandler.setMode(FakeHandler.Mode.QUEUEING)
+
+            // WHEN bouncer show is requested
+            underTest.show(true)
+
+            // WHEN all queued messages are dispatched
+            mainHandler.dispatchQueuedMessages()
+
+            // THEN primary bouncer state doesn't update to show since delegate was null
+            verify(repository, never()).setPrimaryShow(true)
+            verify(repository, never()).setPrimaryShowingSoon(false)
+            verify(mPrimaryBouncerCallbackInteractor, never()).dispatchStartingToShow()
+            verify(mPrimaryBouncerCallbackInteractor, never())
+                .dispatchVisibilityChanged(View.VISIBLE)
+        }
+    }
+
+    @Test
     fun testShow_isScrimmed() {
         underTest.show(true)
         verify(repository).setKeyguardAuthenticatedBiometrics(null)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
index 2018e61..d1b120e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
@@ -28,8 +28,8 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.utils.os.FakeHandler
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.TestScope
@@ -51,8 +51,8 @@
     @Mock private lateinit var primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
     @Mock private lateinit var falsingCollector: FalsingCollector
     @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
-    @Mock private lateinit var keyguardBypassController: KeyguardBypassController
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
     private val mainHandler = FakeHandler(Looper.getMainLooper())
     private lateinit var underTest: PrimaryBouncerInteractor
 
@@ -74,6 +74,7 @@
                 keyguardUpdateMonitor,
                 Mockito.mock(TrustRepository::class.java),
                 TestScope().backgroundScope,
+                mSelectedUserInteractor,
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
index 802f8e6..b75f3e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
@@ -32,8 +32,8 @@
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.utils.os.FakeHandler
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.launchIn
@@ -60,7 +60,7 @@
     @Mock private lateinit var primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
     @Mock private lateinit var falsingCollector: FalsingCollector
     @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
-    @Mock private lateinit var keyguardBypassController: KeyguardBypassController
+    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     private val mainHandler = FakeHandler(Looper.getMainLooper())
     val repository = FakeKeyguardBouncerRepository()
@@ -82,6 +82,7 @@
                 keyguardUpdateMonitor,
                 Mockito.mock(TrustRepository::class.java),
                 TestScope().backgroundScope,
+                mSelectedUserInteractor,
             )
         underTest = KeyguardBouncerViewModel(bouncerView, bouncerInteractor)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
index 037c1ba..6d3cc4c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
@@ -28,11 +28,11 @@
 import com.android.systemui.ActivityIntentHelper
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.KotlinArgumentCaptor
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
@@ -46,8 +46,8 @@
 import org.mockito.Mockito.any
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
 @RunWith(JUnit4::class)
@@ -74,7 +74,7 @@
     @Mock
     lateinit var contentResolver: ContentResolver
     @Mock
-    lateinit var userTracker: UserTracker
+    lateinit var mSelectedUserInteractor: SelectedUserInteractor
 
     private lateinit var underTest: CameraGestureHelper
 
@@ -103,7 +103,7 @@
             cameraIntents = cameraIntents,
             contentResolver = contentResolver,
             uiExecutor = MoreExecutors.directExecutor(),
-            userTracker = userTracker,
+            selectedUserInteractor = mSelectedUserInteractor,
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
index 442bf91..80fe9e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
@@ -40,6 +40,7 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.sensors.ProximitySensor;
 import com.android.systemui.util.sensors.ThresholdSensor;
@@ -75,6 +76,8 @@
     private ShadeExpansionStateManager mShadeExpansionStateManager;
     @Mock
     private BatteryController mBatteryController;
+    @Mock
+    private SelectedUserInteractor mSelectedUserInteractor;
     private final DockManagerFake mDockManager = new DockManagerFake();
     private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
     private final FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock);
@@ -90,7 +93,7 @@
                 mKeyguardUpdateMonitor, mHistoryTracker, mProximitySensor,
                 mStatusBarStateController, mKeyguardStateController, mShadeExpansionStateManager,
                 mBatteryController,
-                mDockManager, mFakeExecutor, mFakeSystemClock);
+                mDockManager, mFakeExecutor, mFakeSystemClock, () -> mSelectedUserInteractor);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java b/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
index 15e5e1c..882bcab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
@@ -17,7 +17,6 @@
 package com.android.systemui.colorextraction;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -37,6 +36,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -63,6 +63,8 @@
     private WallpaperManager mWallpaperManager;
     @Mock
     private DumpManager mDumpManager;
+    @Mock
+    private SelectedUserInteractor mSelectedUserInteractor;
     private ColorExtractor.GradientColors mColors;
     private SysuiColorExtractor mColorExtractor;
 
@@ -83,7 +85,8 @@
                 mock(ConfigurationController.class),
                 mWallpaperManager,
                 mDumpManager,
-                true /* immediately */);
+                true /* immediately */,
+                () -> mSelectedUserInteractor);
     }
 
     @Test
@@ -101,21 +104,6 @@
     }
 
     @Test
-    public void getColors_fallbackWhenMediaIsVisible() {
-        simulateEvent(mColorExtractor);
-        mColorExtractor.setHasMediaArtwork(true);
-
-        ColorExtractor.GradientColors fallbackColors = mColorExtractor.getNeutralColors();
-
-        for (int type : sTypes) {
-            assertEquals("Not using fallback!",
-                    mColorExtractor.getColors(WallpaperManager.FLAG_LOCK, type), fallbackColors);
-            assertNotEquals("Media visibility should not affect system wallpaper.",
-                    mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM, type), fallbackColors);
-        }
-    }
-
-    @Test
     public void onUiModeChanged_reloadsColors() {
         Tonal tonal = mock(Tonal.class);
         ConfigurationController configurationController = mock(ConfigurationController.class);
@@ -125,7 +113,8 @@
                 configurationController,
                 mWallpaperManager,
                 mDumpManager,
-                true /* immediately */);
+                true /* immediately */,
+                () -> mSelectedUserInteractor);
         verify(configurationController).addCallback(eq(sysuiColorExtractor));
 
         reset(tonal);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index 91409a3..fcb191b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -12,9 +12,10 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.communal.data.model.CommunalWidgetMetadata
-import com.android.systemui.communal.shared.CommunalContentSize
+import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.FakeLogBuffer
@@ -38,6 +39,7 @@
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -58,10 +60,16 @@
 
     @Mock private lateinit var userTracker: UserTracker
 
-    @Mock private lateinit var featureFlags: FeatureFlags
+    @Mock private lateinit var featureFlags: FeatureFlagsClassic
 
     @Mock private lateinit var stopwatchProviderInfo: AppWidgetProviderInfo
 
+    @Mock private lateinit var providerInfoA: AppWidgetProviderInfo
+
+    @Mock private lateinit var providerInfoB: AppWidgetProviderInfo
+
+    @Mock private lateinit var providerInfoC: AppWidgetProviderInfo
+
     private lateinit var communalRepository: FakeCommunalRepository
 
     private lateinit var logBuffer: LogBuffer
@@ -70,29 +78,34 @@
 
     private val testScope = TestScope(testDispatcher)
 
+    private val fakeAllowlist =
+        listOf(
+            "com.android.fake/WidgetProviderA",
+            "com.android.fake/WidgetProviderB",
+            "com.android.fake/WidgetProviderC",
+        )
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
         logBuffer = FakeLogBuffer.Factory.create()
-
-        featureFlagEnabled(true)
         communalRepository = FakeCommunalRepository()
-        communalRepository.setIsCommunalEnabled(true)
 
-        overrideResource(
-            R.array.config_communalWidgetAllowlist,
-            arrayOf(componentName1, componentName2)
-        )
+        communalEnabled(true)
+        widgetOnKeyguardEnabled(true)
+        setAppWidgetIds(emptyList())
+
+        overrideResource(R.array.config_communalWidgetAllowlist, fakeAllowlist.toTypedArray())
 
         whenever(stopwatchProviderInfo.loadLabel(any())).thenReturn("Stopwatch")
         whenever(userTracker.userHandle).thenReturn(userHandle)
     }
 
     @Test
-    fun broadcastReceiver_featureDisabled_doNotRegisterUserUnlockedBroadcastReceiver() =
+    fun broadcastReceiver_communalDisabled_doNotRegisterUserUnlockedBroadcastReceiver() =
         testScope.runTest {
-            featureFlagEnabled(false)
+            communalEnabled(false)
             val repository = initCommunalWidgetRepository()
             collectLastValue(repository.stopwatchAppWidgetInfo)()
             verifyBroadcastReceiverNeverRegistered()
@@ -129,7 +142,7 @@
             job.cancel()
             runCurrent()
 
-            Mockito.verify(broadcastDispatcher).unregisterReceiver(receiver)
+            verify(broadcastDispatcher).unregisterReceiver(receiver)
         }
 
     @Test
@@ -166,7 +179,7 @@
             installedProviders(listOf(stopwatchProviderInfo))
             val repository = initCommunalWidgetRepository()
             collectLastValue(repository.stopwatchAppWidgetInfo)()
-            Mockito.verify(appWidgetHost).allocateAppWidgetId()
+            verify(appWidgetHost).allocateAppWidgetId()
         }
 
     @Test
@@ -185,8 +198,8 @@
 
             // Verify app widget id allocated
             assertThat(lastStopwatchProviderInfo()?.appWidgetId).isEqualTo(123456)
-            Mockito.verify(appWidgetHost).allocateAppWidgetId()
-            Mockito.verify(appWidgetHost, Mockito.never()).deleteAppWidgetId(anyInt())
+            verify(appWidgetHost).allocateAppWidgetId()
+            verify(appWidgetHost, Mockito.never()).deleteAppWidgetId(anyInt())
 
             // User locked again
             userUnlocked(false)
@@ -194,7 +207,7 @@
 
             // Verify app widget id deleted
             assertThat(lastStopwatchProviderInfo()).isNull()
-            Mockito.verify(appWidgetHost).deleteAppWidgetId(123456)
+            verify(appWidgetHost).deleteAppWidgetId(123456)
         }
 
     @Test
@@ -203,13 +216,13 @@
             userUnlocked(false)
             val repository = initCommunalWidgetRepository()
             collectLastValue(repository.stopwatchAppWidgetInfo)()
-            Mockito.verify(appWidgetHost, Mockito.never()).startListening()
+            verify(appWidgetHost, Mockito.never()).startListening()
 
             userUnlocked(true)
             broadcastReceiverUpdate()
             collectLastValue(repository.stopwatchAppWidgetInfo)()
 
-            Mockito.verify(appWidgetHost).startListening()
+            verify(appWidgetHost).startListening()
         }
 
     @Test
@@ -223,14 +236,14 @@
             broadcastReceiverUpdate()
             collectLastValue(repository.stopwatchAppWidgetInfo)()
 
-            Mockito.verify(appWidgetHost).startListening()
-            Mockito.verify(appWidgetHost, Mockito.never()).stopListening()
+            verify(appWidgetHost).startListening()
+            verify(appWidgetHost, Mockito.never()).stopListening()
 
             userUnlocked(false)
             broadcastReceiverUpdate()
             collectLastValue(repository.stopwatchAppWidgetInfo)()
 
-            Mockito.verify(appWidgetHost).stopListening()
+            verify(appWidgetHost).stopListening()
         }
 
     @Test
@@ -241,21 +254,80 @@
             assertThat(
                     listOf(
                         CommunalWidgetMetadata(
-                            componentName = componentName1,
-                            priority = 2,
-                            sizes = listOf(CommunalContentSize.HALF)
+                            componentName = fakeAllowlist[0],
+                            priority = 3,
+                            sizes = listOf(CommunalContentSize.HALF),
                         ),
                         CommunalWidgetMetadata(
-                            componentName = componentName2,
+                            componentName = fakeAllowlist[1],
+                            priority = 2,
+                            sizes = listOf(CommunalContentSize.HALF),
+                        ),
+                        CommunalWidgetMetadata(
+                            componentName = fakeAllowlist[2],
                             priority = 1,
-                            sizes = listOf(CommunalContentSize.HALF)
-                        )
+                            sizes = listOf(CommunalContentSize.HALF),
+                        ),
                     )
                 )
                 .containsExactly(*communalWidgetAllowlist.toTypedArray())
         }
     }
 
+    // This behavior is temporary before the local database is set up.
+    @Test
+    fun communalWidgets_withPreviouslyBoundWidgets_removeEachBinding() =
+        testScope.runTest {
+            whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(1, 2, 3)
+            setAppWidgetIds(listOf(1, 2, 3))
+            whenever(appWidgetManager.getAppWidgetInfo(anyInt())).thenReturn(providerInfoA)
+            userUnlocked(true)
+
+            val repository = initCommunalWidgetRepository()
+
+            collectLastValue(repository.communalWidgets)()
+
+            verify(appWidgetHost).deleteAppWidgetId(1)
+            verify(appWidgetHost).deleteAppWidgetId(2)
+            verify(appWidgetHost).deleteAppWidgetId(3)
+        }
+
+    @Test
+    fun communalWidgets_allowlistNotEmpty_bindEachWidgetFromTheAllowlist() =
+        testScope.runTest {
+            whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(0, 1, 2)
+            userUnlocked(true)
+
+            whenever(appWidgetManager.getAppWidgetInfo(0)).thenReturn(providerInfoA)
+            whenever(appWidgetManager.getAppWidgetInfo(1)).thenReturn(providerInfoB)
+            whenever(appWidgetManager.getAppWidgetInfo(2)).thenReturn(providerInfoC)
+
+            val repository = initCommunalWidgetRepository()
+
+            val inventory by collectLastValue(repository.communalWidgets)
+
+            assertThat(
+                    listOf(
+                        CommunalWidgetContentModel(
+                            appWidgetId = 0,
+                            providerInfo = providerInfoA,
+                            priority = 3,
+                        ),
+                        CommunalWidgetContentModel(
+                            appWidgetId = 1,
+                            providerInfo = providerInfoB,
+                            priority = 2,
+                        ),
+                        CommunalWidgetContentModel(
+                            appWidgetId = 2,
+                            providerInfo = providerInfoC,
+                            priority = 1,
+                        ),
+                    )
+                )
+                .containsExactly(*inventory!!.toTypedArray())
+        }
+
     private fun initCommunalWidgetRepository(): CommunalWidgetRepositoryImpl {
         return CommunalWidgetRepositoryImpl(
             context,
@@ -272,7 +344,7 @@
     }
 
     private fun verifyBroadcastReceiverRegistered() {
-        Mockito.verify(broadcastDispatcher)
+        verify(broadcastDispatcher)
             .registerReceiver(
                 any(),
                 any(),
@@ -284,7 +356,7 @@
     }
 
     private fun verifyBroadcastReceiverNeverRegistered() {
-        Mockito.verify(broadcastDispatcher, Mockito.never())
+        verify(broadcastDispatcher, Mockito.never())
             .registerReceiver(
                 any(),
                 any(),
@@ -297,7 +369,7 @@
 
     private fun broadcastReceiverUpdate(): BroadcastReceiver {
         val broadcastReceiverCaptor = kotlinArgumentCaptor<BroadcastReceiver>()
-        Mockito.verify(broadcastDispatcher)
+        verify(broadcastDispatcher)
             .registerReceiver(
                 broadcastReceiverCaptor.capture(),
                 any(),
@@ -310,7 +382,11 @@
         return broadcastReceiverCaptor.value
     }
 
-    private fun featureFlagEnabled(enabled: Boolean) {
+    private fun communalEnabled(enabled: Boolean) {
+        communalRepository.setIsCommunalEnabled(enabled)
+    }
+
+    private fun widgetOnKeyguardEnabled(enabled: Boolean) {
         whenever(featureFlags.isEnabled(Flags.WIDGET_ON_KEYGUARD)).thenReturn(enabled)
     }
 
@@ -322,8 +398,7 @@
         whenever(appWidgetManager.installedProviders).thenReturn(providers)
     }
 
-    companion object {
-        const val componentName1 = "component name 1"
-        const val componentName2 = "component name 2"
+    private fun setAppWidgetIds(ids: List<Int>) {
+        whenever(appWidgetHost.appWidgetIds).thenReturn(ids.toIntArray())
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index cdc42e0..8e21f29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -22,7 +22,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.data.repository.FakeCommunalRepository
 import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
 import com.android.systemui.coroutines.collectLastValue
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
index 7e1edd2..ba578a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
@@ -51,6 +51,7 @@
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.UdfpsController;
 import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.wakelock.WakeLockFake;
 import com.android.systemui.utils.os.FakeHandler;
 
@@ -85,6 +86,8 @@
     private DozeLog mDozeLog;
     @Mock
     private DozeScreenBrightness mDozeScreenBrightness;
+    @Mock
+    private SelectedUserInteractor mSelectedUserInteractor;
 
     @Before
     public void setUp() throws Exception {
@@ -100,7 +103,7 @@
         mWakeLock = new WakeLockFake();
         mScreen = new DozeScreenState(mServiceFake, mHandlerFake, mDozeHost, mDozeParameters,
                 mWakeLock, mAuthController, mUdfpsControllerProvider, mDozeLog,
-                mDozeScreenBrightness);
+                mDozeScreenBrightness, mSelectedUserInteractor);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index a88a8e5..3cc0451 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -52,9 +52,9 @@
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.doze.DozeSensors.TriggerSensor;
 import com.android.systemui.plugins.SensorManagerPlugin;
-import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.policy.DevicePostureController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.sensors.AsyncSensorManager;
 import com.android.systemui.util.sensors.ProximitySensor;
 import com.android.systemui.util.settings.FakeSettings;
@@ -102,7 +102,7 @@
     @Mock
     private DevicePostureController mDevicePostureController;
     @Mock
-    private UserTracker mUserTracker;
+    private SelectedUserInteractor mSelectedUserInteractor;
     @Mock
     private ProximitySensor mProximitySensor;
 
@@ -122,7 +122,8 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mTestableLooper = TestableLooper.get(this);
-        when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
+        when(mSelectedUserInteractor.getSelectedUserId())
+                .thenReturn(ActivityManager.getCurrentUser());
         when(mAmbientDisplayConfiguration.tapSensorTypeMapping())
                 .thenReturn(new String[]{"tapSensor"});
         when(mAmbientDisplayConfiguration.getWakeLockScreenDebounce()).thenReturn(5000L);
@@ -435,7 +436,7 @@
         DozeSensors dozeSensors = new DozeSensors(mResources, mSensorManager, mDozeParameters,
                 mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
                 mProximitySensor, mFakeSettings, mAuthController,
-                mDevicePostureController, mUserTracker);
+                mDevicePostureController, mSelectedUserInteractor);
 
         for (TriggerSensor sensor : dozeSensors.mTriggerSensors) {
             assertFalse(sensor.mIgnoresSetting);
@@ -480,7 +481,7 @@
         DozeSensors dozeSensors = new DozeSensors(mResources, mSensorManager, mDozeParameters,
                 mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
                 mProximitySensor, mFakeSettings, mAuthController,
-                mDevicePostureController, mUserTracker);
+                mDevicePostureController, mSelectedUserInteractor);
 
         for (TriggerSensor sensor : dozeSensors.mTriggerSensors) {
             // THEN lift to wake's TriggerSensor enabledBySettings is false
@@ -499,7 +500,7 @@
         DozeSensors dozeSensors = new DozeSensors(mResources, mSensorManager, mDozeParameters,
                 mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
                 mProximitySensor, mFakeSettings, mAuthController,
-                mDevicePostureController, mUserTracker);
+                mDevicePostureController, mSelectedUserInteractor);
 
         for (TriggerSensor sensor : dozeSensors.mTriggerSensors) {
             // THEN lift to wake's TriggerSensor enabledBySettings is true
@@ -514,7 +515,7 @@
             super(mResources, mSensorManager, mDozeParameters,
                     mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
                     mProximitySensor, mFakeSettings, mAuthController,
-                    mDevicePostureController, mUserTracker);
+                    mDevicePostureController, mSelectedUserInteractor);
             for (TriggerSensor sensor : mTriggerSensors) {
                 if (sensor instanceof PluginSensor
                         && ((PluginSensor) sensor).mPluginSensor.getType()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 494e230..3a6b075 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -53,6 +53,7 @@
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.concurrency.FakeThreadFactory;
 import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -101,6 +102,8 @@
     @Mock
     private UserTracker mUserTracker;
     @Mock
+    private SelectedUserInteractor mSelectedUserInteractor;
+    @Mock
     private SessionTracker mSessionTracker;
 
     private DozeTriggers mTriggers;
@@ -134,7 +137,7 @@
                 asyncSensorManager, wakeLock, mDockManager, mProximitySensor,
                 mProximityCheck, mDozeLog, mBroadcastDispatcher, new FakeSettings(),
                 mAuthController, mUiEventLogger, mSessionTracker, mKeyguardStateController,
-                mDevicePostureController, mUserTracker);
+                mDevicePostureController, mUserTracker, mSelectedUserInteractor);
         mTriggers.setDozeMachine(mMachine);
         waitForSensorManager();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index 2d3f69d..00009f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -76,6 +76,7 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
 import com.android.systemui.telephony.TelephonyListenerManager;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.RingerModeLiveData;
 import com.android.systemui.util.RingerModeTracker;
 import com.android.systemui.util.settings.FakeGlobalSettings;
@@ -134,6 +135,7 @@
     @Mock private ShadeController mShadeController;
     @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock private DialogLaunchAnimator mDialogLaunchAnimator;
+    @Mock private SelectedUserInteractor mSelectedUserInteractor;
     @Mock private OnBackInvokedDispatcher mOnBackInvokedDispatcher;
     @Captor private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback;
 
@@ -186,12 +188,14 @@
                 mPackageManager,
                 mShadeController,
                 mKeyguardUpdateMonitor,
-                mDialogLaunchAnimator);
+                mDialogLaunchAnimator,
+                mSelectedUserInteractor);
         mGlobalActionsDialogLite.setZeroDialogPressDelayForTesting();
 
         ColorExtractor.GradientColors backdropColors = new ColorExtractor.GradientColors();
         backdropColors.setMainColor(Color.BLACK);
         when(mColorExtractor.getNeutralColors()).thenReturn(backdropColors);
+        when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(0);
     }
 
     @Test
@@ -568,7 +572,8 @@
     @Test
     public void testOnLockScreen_disableSmartLock() {
         mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
-        int user = KeyguardUpdateMonitor.getCurrentUser();
+        int expectedUser = 100;
+        doReturn(expectedUser).when(mSelectedUserInteractor).getSelectedUserId();
         doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems();
         doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any());
         doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any());
@@ -586,7 +591,7 @@
         mGlobalActionsDialogLite.showOrHideDialog(true, true, null /* view */);
 
         // Then smart lock will be disabled
-        verify(mLockPatternUtils).requireCredentialEntry(eq(user));
+        verify(mLockPatternUtils).requireCredentialEntry(eq(expectedUser));
 
         // hide dialog again
         mGlobalActionsDialogLite.showOrHideDialog(true, true, null /* view */);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
index 7a13a0a..489665c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
@@ -106,7 +106,6 @@
 
         whenever(keyguardViewController.viewRootImpl).thenReturn(mock(ViewRootImpl::class.java))
         whenever(powerManager.isInteractive).thenReturn(true)
-        whenever(wallpaperManager.isLockscreenLiveWallpaperEnabled).thenReturn(false)
 
         // All of these fields are final, so we can't mock them, but are needed so that the surface
         // appear amount setter doesn't short circuit.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index a646823..2b280c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -117,6 +117,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.DeviceConfigProxyFake;
 import com.android.systemui.util.concurrency.FakeExecutor;
@@ -187,6 +188,7 @@
     private @Mock ShadeExpansionStateManager mShadeExpansionStateManager;
     private @Mock ShadeInteractor mShadeInteractor;
     private @Mock ShadeWindowLogger mShadeWindowLogger;
+    private @Mock SelectedUserInteractor mSelectedUserInteractor;
     private @Captor ArgumentCaptor<KeyguardStateController.Callback>
             mKeyguardStateControllerCallback;
     private @Captor ArgumentCaptor<KeyguardUpdateMonitorCallback>
@@ -213,7 +215,7 @@
     private @Mock SystemPropertiesHelper mSystemPropertiesHelper;
 
     private FakeFeatureFlags mFeatureFlags;
-    private int mInitialUserId;
+    private final int mDefaultUserId = 100;
 
     @Before
     public void setUp() throws Exception {
@@ -233,6 +235,8 @@
                 .thenReturn(mock(Flow.class));
         when(mDreamingToLockscreenTransitionViewModel.getTransitionEnded())
                 .thenReturn(mock(Flow.class));
+        when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(mDefaultUserId);
+        when(mSelectedUserInteractor.getSelectedUserId(anyBoolean())).thenReturn(mDefaultUserId);
         mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(
                 mContext,
                 new FakeWindowRootViewComponent.Factory(mock(WindowRootView.class)),
@@ -251,9 +255,11 @@
                 mAuthController,
                 mShadeExpansionStateManager,
                 () -> mShadeInteractor,
-                mShadeWindowLogger);
+                mShadeWindowLogger,
+                () -> mSelectedUserInteractor);
         mFeatureFlags = new FakeFeatureFlags();
         mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
+        mFeatureFlags.set(Flags.REFACTOR_GETCURRENTUSER, true);
 
         DejankUtils.setImmediate(true);
 
@@ -266,12 +272,6 @@
         }).when(mKeyguardStateController).notifyKeyguardGoingAway(anyBoolean());
 
         createAndStartViewMediator();
-        mInitialUserId = KeyguardUpdateMonitor.getCurrentUser();
-    }
-
-    @After
-    public void teardown() {
-        KeyguardUpdateMonitor.setCurrentUser(mInitialUserId);
     }
 
     /**
@@ -451,7 +451,7 @@
         mViewMediator.setKeyguardEnabled(false);
         TestableLooper.get(this).processAllMessages();
 
-        mViewMediator.mViewMediatorCallback.keyguardDonePending(mUpdateMonitor.getCurrentUser());
+        mViewMediator.mViewMediatorCallback.keyguardDonePending(mDefaultUserId);
         mViewMediator.mViewMediatorCallback.readyForKeyguardDone();
         final ArgumentCaptor<Runnable> animationRunnableCaptor =
                 ArgumentCaptor.forClass(Runnable.class);
@@ -617,8 +617,8 @@
     public void lockAfterScreenTimeoutUsesValueFromSettings() {
         int currentUserId = 99;
         int userSpecificTimeout = 5999;
-        KeyguardUpdateMonitor.setCurrentUser(currentUserId);
 
+        when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(currentUserId);
         when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false);
         when(mDevicePolicyManager.getMaximumTimeToLock(null, currentUserId)).thenReturn(0L);
         when(mSecureSettings.getIntForUser(LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
@@ -718,7 +718,7 @@
         startMockKeyguardExitAnimation();
         assertTrue(mViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehind());
 
-        mViewMediator.mViewMediatorCallback.keyguardDonePending(mUpdateMonitor.getCurrentUser());
+        mViewMediator.mViewMediatorCallback.keyguardDonePending(mDefaultUserId);
         mViewMediator.mViewMediatorCallback.readyForKeyguardDone();
         TestableLooper.get(this).processAllMessages();
         verify(mKeyguardUnlockAnimationController).notifyFinishedKeyguardExitAnimation(false);
@@ -782,7 +782,7 @@
 
         // Verify keyguard told of authentication
         verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean());
-        mViewMediator.mViewMediatorCallback.keyguardDonePending(mUpdateMonitor.getCurrentUser());
+        mViewMediator.mViewMediatorCallback.keyguardDonePending(mDefaultUserId);
         mViewMediator.mViewMediatorCallback.readyForKeyguardDone();
         final ArgumentCaptor<Runnable> animationRunnableCaptor =
                 ArgumentCaptor.forClass(Runnable.class);
@@ -814,7 +814,7 @@
         // Verify keyguard told of authentication
         verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean());
         clearInvocations(mStatusBarKeyguardViewManager);
-        mViewMediator.mViewMediatorCallback.keyguardDonePending(mUpdateMonitor.getCurrentUser());
+        mViewMediator.mViewMediatorCallback.keyguardDonePending(mDefaultUserId);
         mViewMediator.mViewMediatorCallback.readyForKeyguardDone();
         final ArgumentCaptor<Runnable> animationRunnableCaptor =
                 ArgumentCaptor.forClass(Runnable.class);
@@ -844,7 +844,7 @@
 
         // Verify keyguard told of authentication
         verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean());
-        mViewMediator.mViewMediatorCallback.keyguardDonePending(mUpdateMonitor.getCurrentUser());
+        mViewMediator.mViewMediatorCallback.keyguardDonePending(mDefaultUserId);
         mViewMediator.mViewMediatorCallback.readyForKeyguardDone();
         final ArgumentCaptor<Runnable> animationRunnableCaptor =
                 ArgumentCaptor.forClass(Runnable.class);
@@ -1111,7 +1111,8 @@
                 mDispatcher,
                 () -> mDreamingToLockscreenTransitionViewModel,
                 mSystemPropertiesHelper,
-                () -> mock(WindowManagerLockscreenVisibilityManager.class));
+                () -> mock(WindowManagerLockscreenVisibilityManager.class),
+                mSelectedUserInteractor);
         mViewMediator.start();
 
         mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index b9119e1..ef03fdf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -30,14 +30,10 @@
 import com.android.systemui.doze.DozeTransitionCallback
 import com.android.systemui.doze.DozeTransitionListener
 import com.android.systemui.dreams.DreamOverlayCallbackController
-import com.android.systemui.keyguard.ScreenLifecycle
-import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.phone.BiometricUnlockController
-import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
@@ -47,7 +43,6 @@
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onCompletion
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
@@ -68,13 +63,10 @@
 
     @Mock private lateinit var statusBarStateController: StatusBarStateController
     @Mock private lateinit var keyguardStateController: KeyguardStateController
-    @Mock private lateinit var screenLifecycle: ScreenLifecycle
-    @Mock private lateinit var biometricUnlockController: BiometricUnlockController
     @Mock private lateinit var dozeTransitionListener: DozeTransitionListener
     @Mock private lateinit var authController: AuthController
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var dreamOverlayCallbackController: DreamOverlayCallbackController
-    @Mock private lateinit var dozeParameters: DozeParameters
     private val mainDispatcher = StandardTestDispatcher()
     private val testDispatcher = StandardTestDispatcher()
     private val testScope = TestScope(testDispatcher)
@@ -89,12 +81,9 @@
         underTest =
             KeyguardRepositoryImpl(
                 statusBarStateController,
-                screenLifecycle,
-                biometricUnlockController,
                 keyguardStateController,
                 keyguardUpdateMonitor,
                 dozeTransitionListener,
-                dozeParameters,
                 authController,
                 dreamOverlayCallbackController,
                 mainDispatcher,
@@ -201,26 +190,6 @@
         }
 
     @Test
-    fun isAodAvailable() = runTest {
-        val flow = underTest.isAodAvailable
-        val isAodAvailable = collectLastValue(flow)
-        runCurrent()
-
-        val callback =
-            withArgCaptor<DozeParameters.Callback> { verify(dozeParameters).addCallback(capture()) }
-
-        whenever(dozeParameters.getAlwaysOn()).thenReturn(false)
-        callback.onAlwaysOnChange()
-        assertThat(isAodAvailable()).isEqualTo(false)
-
-        whenever(dozeParameters.getAlwaysOn()).thenReturn(true)
-        callback.onAlwaysOnChange()
-        assertThat(isAodAvailable()).isEqualTo(true)
-
-        flow.onCompletion { verify(dozeParameters).removeCallback(callback) }
-    }
-
-    @Test
     fun isKeyguardOccluded() =
         testScope.runTest {
             whenever(keyguardStateController.isOccluded).thenReturn(false)
@@ -386,53 +355,6 @@
         }
 
     @Test
-    fun biometricUnlockState() =
-        testScope.runTest {
-            val values = mutableListOf<BiometricUnlockModel>()
-            val job = underTest.biometricUnlockState.onEach(values::add).launchIn(this)
-
-            runCurrent()
-            val captor = argumentCaptor<BiometricUnlockController.BiometricUnlockEventsListener>()
-            verify(biometricUnlockController).addListener(captor.capture())
-
-            listOf(
-                    BiometricUnlockController.MODE_NONE,
-                    BiometricUnlockController.MODE_WAKE_AND_UNLOCK,
-                    BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING,
-                    BiometricUnlockController.MODE_SHOW_BOUNCER,
-                    BiometricUnlockController.MODE_ONLY_WAKE,
-                    BiometricUnlockController.MODE_UNLOCK_COLLAPSING,
-                    BiometricUnlockController.MODE_DISMISS_BOUNCER,
-                    BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM,
-                )
-                .forEach {
-                    whenever(biometricUnlockController.mode).thenReturn(it)
-                    captor.value.onModeChanged(it)
-                    runCurrent()
-                }
-
-            assertThat(values)
-                .isEqualTo(
-                    listOf(
-                        // Initial value will be NONE, followed by onModeChanged() call
-                        BiometricUnlockModel.NONE,
-                        BiometricUnlockModel.NONE,
-                        BiometricUnlockModel.WAKE_AND_UNLOCK,
-                        BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING,
-                        BiometricUnlockModel.SHOW_BOUNCER,
-                        BiometricUnlockModel.ONLY_WAKE,
-                        BiometricUnlockModel.UNLOCK_COLLAPSING,
-                        BiometricUnlockModel.DISMISS_BOUNCER,
-                        BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM,
-                    )
-                )
-
-            job.cancel()
-            runCurrent()
-            verify(biometricUnlockController).removeListener(captor.value)
-        }
-
-    @Test
     fun dozeTransitionModel() =
         testScope.runTest {
             // For the initial state
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index 5afc405..ad80a06 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.keyguard.util.KeyguardTransitionRunner
@@ -86,7 +87,7 @@
         }
 
     @Test
-    fun startingSecondTransitionWillCancelTheFirstTransition() =
+    fun startingSecondTransitionWillCancelTheFirstTransitionAndUseLastValue() =
         TestScope().runTest {
             val steps = mutableListOf<TransitionStep>()
             val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
@@ -100,12 +101,19 @@
             val job2 = underTest.transition(LOCKSCREEN, AOD).onEach { steps.add(it) }.launchIn(this)
             runner.startTransition(
                 this,
-                TransitionInfo(OWNER_NAME, LOCKSCREEN, AOD, getAnimator()),
+                TransitionInfo(
+                    OWNER_NAME,
+                    LOCKSCREEN,
+                    AOD,
+                    getAnimator(),
+                    TransitionModeOnCanceled.LAST_VALUE
+                ),
             )
 
             val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.1))
             assertSteps(steps.subList(0, 4), firstTransitionSteps, AOD, LOCKSCREEN)
 
+            // Second transition starts from .1 (LAST_VALUE)
             val secondTransitionSteps = listWithStep(step = BigDecimal(.1), start = BigDecimal(.1))
             assertSteps(steps.subList(4, steps.size), secondTransitionSteps, LOCKSCREEN, AOD)
 
@@ -114,6 +122,76 @@
         }
 
     @Test
+    fun startingSecondTransitionWillCancelTheFirstTransitionAndUseReset() =
+        TestScope().runTest {
+            val steps = mutableListOf<TransitionStep>()
+            val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
+            runner.startTransition(
+                this,
+                TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, getAnimator()),
+                maxFrames = 3,
+            )
+
+            // Now start 2nd transition, which will interrupt the first
+            val job2 = underTest.transition(LOCKSCREEN, AOD).onEach { steps.add(it) }.launchIn(this)
+            runner.startTransition(
+                this,
+                TransitionInfo(
+                    OWNER_NAME,
+                    LOCKSCREEN,
+                    AOD,
+                    getAnimator(),
+                    TransitionModeOnCanceled.RESET
+                ),
+            )
+
+            val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.1))
+            assertSteps(steps.subList(0, 4), firstTransitionSteps, AOD, LOCKSCREEN)
+
+            // Second transition starts from 0 (RESET)
+            val secondTransitionSteps = listWithStep(start = BigDecimal(0), step = BigDecimal(.1))
+            assertSteps(steps.subList(4, steps.size), secondTransitionSteps, LOCKSCREEN, AOD)
+
+            job.cancel()
+            job2.cancel()
+        }
+
+    @Test
+    fun startingSecondTransitionWillCancelTheFirstTransitionAndUseReverse() =
+        TestScope().runTest {
+            val steps = mutableListOf<TransitionStep>()
+            val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
+            runner.startTransition(
+                this,
+                TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, getAnimator()),
+                maxFrames = 3,
+            )
+
+            // Now start 2nd transition, which will interrupt the first
+            val job2 = underTest.transition(LOCKSCREEN, AOD).onEach { steps.add(it) }.launchIn(this)
+            runner.startTransition(
+                this,
+                TransitionInfo(
+                    OWNER_NAME,
+                    LOCKSCREEN,
+                    AOD,
+                    getAnimator(),
+                    TransitionModeOnCanceled.REVERSE
+                ),
+            )
+
+            val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.1))
+            assertSteps(steps.subList(0, 4), firstTransitionSteps, AOD, LOCKSCREEN)
+
+            // Second transition starts from .9 (REVERSE)
+            val secondTransitionSteps = listWithStep(start = BigDecimal(0.9), step = BigDecimal(.1))
+            assertSteps(steps.subList(4, steps.size), secondTransitionSteps, LOCKSCREEN, AOD)
+
+            job.cancel()
+            job2.cancel()
+        }
+
+    @Test
     fun nullAnimatorEnablesManualControlWithUpdateTransition() =
         TestScope().runTest {
             val steps = mutableListOf<TransitionStep>()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
index 5bd747f..8dea57c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
@@ -20,10 +20,14 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.mock
 import dagger.Lazy
 import junit.framework.Assert.assertEquals
@@ -42,6 +46,12 @@
 class FromPrimaryBouncerTransitionInteractorTest : KeyguardTransitionInteractorTestCase() {
     private lateinit var underTest: FromPrimaryBouncerTransitionInteractor
 
+    private val mSelectedUserInteractor =
+        SelectedUserInteractor(
+            FakeUserRepository(),
+            FakeFeatureFlagsClassic().apply { set(Flags.REFACTOR_GETCURRENTUSER, true) }
+        )
+
     // Override the fromPrimaryBouncerTransitionInteractor provider from the superclass so our
     // underTest interactor is provided to any classes that need it.
     override var fromPrimaryBouncerTransitionInteractorLazy:
@@ -63,6 +73,7 @@
                 flags = FakeFeatureFlags(),
                 keyguardSecurityModel = mock(),
                 powerInteractor = PowerInteractorFactory.create().powerInteractor,
+                selectedUserInteractor = mSelectedUserInteractor
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
index d6e19cb..e87adf5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
@@ -64,8 +64,6 @@
             KeyguardDismissInteractorFactory.create(
                 context = context,
                 testScope = testScope,
-                broadcastDispatcher = fakeBroadcastDispatcher,
-                dispatcher = dispatcher,
             )
         keyguardRepository = dismissInteractorWithDependencies.keyguardRepository
         transitionRepository = FakeKeyguardTransitionRepository()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt
index a5cfbbf..ecb46bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt
@@ -58,8 +58,6 @@
             KeyguardDismissInteractorFactory.create(
                 context = context,
                 testScope = testScope,
-                broadcastDispatcher = fakeBroadcastDispatcher,
-                dispatcher = dispatcher,
             )
         underTest = underTestDependencies.interactor
         underTestDependencies.userRepository.setUserInfos(listOf(userInfo))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
index 6ad2d73..e45f56a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
@@ -62,6 +62,7 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -99,6 +100,7 @@
 
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig
+    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
 
     @Before
     fun setup() {
@@ -145,6 +147,7 @@
                     keyguardUpdateMonitor,
                     FakeTrustRepository(),
                     testScope.backgroundScope,
+                    mSelectedUserInteractor,
                 ),
                 AlternateBouncerInteractor(
                     mock(StatusBarStateController::class.java),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 5d5ece0..275ac80 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -42,6 +42,7 @@
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
 import com.android.systemui.shade.data.repository.FakeShadeRepository
 import com.android.systemui.shade.domain.model.ShadeModel
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.mockito.withArgCaptor
@@ -56,7 +57,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
-import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
 import org.mockito.Mockito.clearInvocations
@@ -86,6 +86,7 @@
 
     // Used to verify transition requests for test output
     @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
+    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
 
     private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
     private lateinit var fromDreamingTransitionInteractor: FromDreamingTransitionInteractor
@@ -158,6 +159,7 @@
                     flags = featureFlags,
                     keyguardSecurityModel = keyguardSecurityModel,
                     powerInteractor = powerInteractor,
+                    selectedUserInteractor = mSelectedUserInteractor,
                 )
                 .apply { start() }
 
@@ -241,7 +243,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to PRIMARY_BOUNCER should occur
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -268,7 +270,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -295,7 +297,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -325,7 +327,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DREAMING should occur
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -356,7 +358,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DREAMING_LOCKSCREEN_HOSTED should occur
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -383,7 +385,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -410,7 +412,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -443,7 +445,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to Lockscreen should occur
             assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
@@ -471,7 +473,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to Gone should occur
             assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
@@ -501,7 +503,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to PRIMARY_BOUNCER should occur
             assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
@@ -534,7 +536,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
@@ -566,7 +568,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to OCCLUDED should occur
             assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
@@ -590,7 +592,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
@@ -622,7 +624,7 @@
             advanceUntilIdle()
 
             // THEN the transition is ignored
-            verify(transitionRepository, never()).startTransition(any(), anyBoolean())
+            verify(transitionRepository, never()).startTransition(any())
 
             coroutineContext.cancelChildren()
         }
@@ -639,7 +641,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
@@ -666,7 +668,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -693,7 +695,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to AOD should occur
             assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -716,7 +718,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to AOD should occur
             assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -746,7 +748,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DREAMING should occur
             assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -777,7 +779,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DREAMING_LOCKSCREEN_HOSTED should occur
             assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -803,7 +805,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to PRIMARY_BOUNCER should occur
             assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -835,7 +837,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to AOD should occur
             assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -868,7 +870,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -898,7 +900,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to LOCKSCREEN should occur
             assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -926,7 +928,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to AOD should occur
             assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -954,7 +956,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -978,7 +980,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to LOCKSCREEN should occur
             assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -1008,7 +1010,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition back to DREAMING_LOCKSCREEN_HOSTED should occur
             assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -1039,7 +1041,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to GONE should occur
             assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -1068,7 +1070,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to LOCKSCREEN should occur
             assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -1093,7 +1095,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to AlternateBouncer should occur
             assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -1118,7 +1120,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to AlternateBouncer should occur
             assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -1144,7 +1146,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to OCCLUDED should occur
             assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -1169,7 +1171,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to OCCLUDED should occur
             assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
@@ -1196,7 +1198,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to OCCLUDED should occur
             assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
@@ -1220,7 +1222,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to OCCLUDED should occur
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -1250,7 +1252,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to OCCLUDED should occur
             assertThat(info.ownerName).isEqualTo("FromAodTransitionInteractor")
@@ -1284,7 +1286,7 @@
             // THEN a transition from LOCKSCREEN => OCCLUDED should occur
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
             assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
@@ -1315,7 +1317,7 @@
             // THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
             assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
@@ -1336,7 +1338,7 @@
             // THEN a transition from PRIMARY_BOUNCER => LOCKSCREEN should occur
             val info2 =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             assertThat(info2.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
             assertThat(info2.to).isEqualTo(KeyguardState.LOCKSCREEN)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
index 02c98cd..f9362a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
@@ -46,6 +46,7 @@
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction
 import com.android.systemui.power.data.repository.FakePowerRepository
 import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -90,6 +91,7 @@
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var mockedContext: Context
     @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
 
     @Before
     fun setup() {
@@ -145,6 +147,7 @@
                     keyguardUpdateMonitor,
                     trustRepository,
                     testScope.backgroundScope,
+                    mSelectedUserInteractor
                 ),
                 AlternateBouncerInteractor(
                     statusBarStateController = mock(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
new file mode 100644
index 0000000..1ff46db
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
@@ -0,0 +1,177 @@
+/*
+ * 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 androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
+import com.android.systemui.keyguard.shared.model.ScrimAlpha
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.util.mockito.whenever
+import com.google.common.collect.Range
+import com.google.common.truth.Truth.assertThat
+import dagger.Lazy
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BouncerToGoneFlowsTest : SysuiTestCase() {
+    private lateinit var underTest: BouncerToGoneFlows
+    private lateinit var repository: FakeKeyguardTransitionRepository
+    private lateinit var featureFlags: FakeFeatureFlags
+    @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+    @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
+    @Mock
+    private lateinit var keyguardDismissActionInteractor: Lazy<KeyguardDismissActionInteractor>
+    @Mock private lateinit var shadeInteractor: ShadeInteractor
+
+    private val shadeExpansionStateFlow = MutableStateFlow(0.1f)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(shadeInteractor.shadeExpansion).thenReturn(shadeExpansionStateFlow)
+
+        repository = FakeKeyguardTransitionRepository()
+        val featureFlags =
+            FakeFeatureFlags().apply { set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false) }
+        val interactor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = TestScope().backgroundScope,
+                    repository = repository,
+                )
+                .keyguardTransitionInteractor
+        underTest =
+            BouncerToGoneFlows(
+                interactor,
+                statusBarStateController,
+                primaryBouncerInteractor,
+                keyguardDismissActionInteractor,
+                featureFlags,
+                shadeInteractor,
+            )
+
+        whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(false)
+        whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false)
+    }
+
+    @Test
+    fun scrimAlpha_runDimissFromKeyguard_shadeExpanded() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
+            shadeExpansionStateFlow.value = 1f
+            whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
+
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            repository.sendTransitionStep(step(0.3f))
+            repository.sendTransitionStep(step(0.6f))
+            repository.sendTransitionStep(step(1f))
+
+            assertThat(values.size).isEqualTo(4)
+            values.forEach { assertThat(it.frontAlpha).isEqualTo(0f) }
+            values.forEach { assertThat(it.behindAlpha).isIn(Range.closed(0f, 1f)) }
+            values.forEach { assertThat(it.notificationsAlpha).isIn(Range.closed(0f, 1f)) }
+        }
+
+    @Test
+    fun scrimAlpha_runDimissFromKeyguard_shadeNotExpanded() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
+            shadeExpansionStateFlow.value = 0f
+
+            whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
+
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            repository.sendTransitionStep(step(0.3f))
+            repository.sendTransitionStep(step(0.6f))
+            repository.sendTransitionStep(step(1f))
+
+            assertThat(values.size).isEqualTo(4)
+            values.forEach { assertThat(it).isEqualTo(ScrimAlpha()) }
+        }
+
+    @Test
+    fun scrimBehindAlpha_leaveShadeOpen() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
+
+            whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(true)
+
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            repository.sendTransitionStep(step(0.3f))
+            repository.sendTransitionStep(step(0.6f))
+            repository.sendTransitionStep(step(1f))
+
+            assertThat(values.size).isEqualTo(4)
+            values.forEach {
+                assertThat(it).isEqualTo(ScrimAlpha(notificationsAlpha = 1f, behindAlpha = 1f))
+            }
+        }
+
+    @Test
+    fun scrimBehindAlpha_doNotLeaveShadeOpen() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
+
+            whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false)
+
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            repository.sendTransitionStep(step(0.3f))
+            repository.sendTransitionStep(step(0.6f))
+            repository.sendTransitionStep(step(1f))
+
+            assertThat(values.size).isEqualTo(4)
+            values.forEach { assertThat(it.notificationsAlpha).isEqualTo(0f) }
+            values.forEach { assertThat(it.frontAlpha).isEqualTo(0f) }
+            values.forEach { assertThat(it.behindAlpha).isIn(Range.closed(0f, 1f)) }
+            assertThat(values[3].behindAlpha).isEqualTo(0f)
+        }
+
+    private fun step(
+        value: Float,
+        state: TransitionState = TransitionState.RUNNING
+    ): TransitionStep {
+        return TransitionStep(
+            from = KeyguardState.PRIMARY_BOUNCER,
+            to = KeyguardState.GONE,
+            value = value,
+            transitionState = state,
+            ownerName = "PrimaryBouncerToGoneTransitionViewModelTest"
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index d7802aa..6cab023 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -27,7 +27,6 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.ScrimAlpha
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -35,6 +34,7 @@
 import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
 import dagger.Lazy
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
@@ -52,12 +52,16 @@
     private lateinit var featureFlags: FakeFeatureFlags
     @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
     @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
+    @Mock private lateinit var bouncerToGoneFlows: BouncerToGoneFlows
     @Mock
     private lateinit var keyguardDismissActionInteractor: Lazy<KeyguardDismissActionInteractor>
 
+    private val shadeExpansionStateFlow = MutableStateFlow(0.1f)
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+
         repository = FakeKeyguardTransitionRepository()
         val featureFlags =
             FakeFeatureFlags().apply { set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false) }
@@ -74,6 +78,7 @@
                 primaryBouncerInteractor,
                 keyguardDismissActionInteractor,
                 featureFlags,
+                bouncerToGoneFlows,
             )
 
         whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(false)
@@ -148,59 +153,6 @@
             values.forEach { assertThat(it).isEqualTo(1f) }
         }
 
-    @Test
-    fun scrimAlpha_runDimissFromKeyguard() =
-        runTest(UnconfinedTestDispatcher()) {
-            val values by collectValues(underTest.scrimAlpha)
-
-            whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
-
-            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
-            repository.sendTransitionStep(step(0.3f))
-            repository.sendTransitionStep(step(0.6f))
-            repository.sendTransitionStep(step(1f))
-
-            assertThat(values.size).isEqualTo(4)
-            values.forEach { assertThat(it).isEqualTo(ScrimAlpha()) }
-        }
-
-    @Test
-    fun scrimBehindAlpha_leaveShadeOpen() =
-        runTest(UnconfinedTestDispatcher()) {
-            val values by collectValues(underTest.scrimAlpha)
-
-            whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(true)
-
-            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
-            repository.sendTransitionStep(step(0.3f))
-            repository.sendTransitionStep(step(0.6f))
-            repository.sendTransitionStep(step(1f))
-
-            assertThat(values.size).isEqualTo(4)
-            values.forEach {
-                assertThat(it).isEqualTo(ScrimAlpha(notificationsAlpha = 1f, behindAlpha = 1f))
-            }
-        }
-
-    @Test
-    fun scrimBehindAlpha_doNotLeaveShadeOpen() =
-        runTest(UnconfinedTestDispatcher()) {
-            val values by collectValues(underTest.scrimAlpha)
-
-            whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false)
-
-            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
-            repository.sendTransitionStep(step(0.3f))
-            repository.sendTransitionStep(step(0.6f))
-            repository.sendTransitionStep(step(1f))
-
-            assertThat(values.size).isEqualTo(4)
-            values.forEach { assertThat(it.notificationsAlpha).isEqualTo(0f) }
-            values.forEach { assertThat(it.frontAlpha).isEqualTo(0f) }
-            values.forEach { assertThat(it.behindAlpha).isIn(Range.closed(0f, 1f)) }
-            assertThat(values[3].behindAlpha).isEqualTo(0f)
-        }
-
     private fun step(
         value: Float,
         state: TransitionState = TransitionState.RUNNING
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
index 54fc493..1abb441 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
@@ -42,7 +42,7 @@
     private var frameCount = 1L
     private var frames = MutableStateFlow(Pair<Long, FrameCallback?>(0L, null))
     private var job: Job? = null
-    private var isTerminated = false
+    @Volatile private var isTerminated = false
 
     /**
      * For transitions being directed by an animator. Will control the number of frames being
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt
new file mode 100644
index 0000000..429aad1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt
@@ -0,0 +1,66 @@
+package com.android.systemui.mediaprojection
+
+import android.media.projection.IMediaProjectionManager
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_APP as METRICS_CREATION_SOURCE_APP
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_CAST as METRICS_CREATION_SOURCE_CAST
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER as METRICS_CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN as METRICS_CREATION_SOURCE_UNKNOWN
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class MediaProjectionMetricsLoggerTest : SysuiTestCase() {
+
+    private val service = mock<IMediaProjectionManager>()
+    private val logger = MediaProjectionMetricsLogger(service)
+
+    @Test
+    fun notifyProjectionInitiated_sourceApp_forwardsToServiceWithMetricsValue() {
+        val hostUid = 123
+        val sessionCreationSource = SessionCreationSource.APP
+
+        logger.notifyProjectionInitiated(hostUid, sessionCreationSource)
+
+        verify(service).notifyPermissionRequestInitiated(hostUid, METRICS_CREATION_SOURCE_APP)
+    }
+
+    @Test
+    fun notifyProjectionInitiated_sourceCast_forwardsToServiceWithMetricsValue() {
+        val hostUid = 123
+        val sessionCreationSource = SessionCreationSource.CAST
+
+        logger.notifyProjectionInitiated(hostUid, sessionCreationSource)
+
+        verify(service).notifyPermissionRequestInitiated(hostUid, METRICS_CREATION_SOURCE_CAST)
+    }
+
+    @Test
+    fun notifyProjectionInitiated_sourceSysUI_forwardsToServiceWithMetricsValue() {
+        val hostUid = 123
+        val sessionCreationSource = SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER
+
+        logger.notifyProjectionInitiated(hostUid, sessionCreationSource)
+
+        verify(service)
+            .notifyPermissionRequestInitiated(
+                hostUid,
+                METRICS_CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER
+            )
+    }
+
+    @Test
+    fun notifyProjectionInitiated_sourceUnknown_forwardsToServiceWithMetricsValue() {
+        val hostUid = 123
+        val sessionCreationSource = SessionCreationSource.UNKNOWN
+
+        logger.notifyProjectionInitiated(hostUid, sessionCreationSource)
+
+        verify(service).notifyPermissionRequestInitiated(hostUid, METRICS_CREATION_SOURCE_UNKNOWN)
+    }
+}
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 9b1f830..0e6e4fa 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
@@ -36,7 +36,6 @@
 import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig
 import com.android.systemui.res.R
 import com.android.systemui.security.data.model.SecurityModel
-import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.statusbar.policy.FakeSecurityController
 import com.android.systemui.statusbar.policy.FakeUserInfoController
 import com.android.systemui.statusbar.policy.FakeUserInfoController.FakeInfo
@@ -130,7 +129,6 @@
         val userInfoController = FakeUserInfoController(FakeInfo(picture = picture))
         val settings = FakeGlobalSettings()
         val userId = 42
-        val userTracker = FakeUserTracker(userId)
         val userSwitcherControllerWrapper =
             MockUserSwitcherControllerWrapper(currentUserName = "foo")
 
@@ -148,7 +146,6 @@
                     utils.footerActionsInteractor(
                         userSwitcherRepository =
                             utils.userSwitcherRepository(
-                                userTracker = userTracker,
                                 settings = settings,
                                 userManager = userManager,
                                 userInfoController = userInfoController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
index 0552ced..0e4b113 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
@@ -124,11 +124,44 @@
     }
 
     @Test
-    fun testLongClickIntent() {
+    fun testLongClickIntent_safetyCenterEnabled() {
         whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(true)
-        assertThat(tile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS)
+        val cameraTile = CameraToggleTile(
+                host,
+                uiEventLogger,
+                testableLooper.looper,
+                Handler(testableLooper.looper),
+                metricsLogger,
+                FalsingManagerFake(),
+                statusBarStateController,
+                activityStarter,
+                qsLogger,
+                privacyController,
+                keyguardStateController,
+                safetyCenterManager)
+        assertThat(cameraTile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS)
+        cameraTile.destroy()
+        testableLooper.processAllMessages()
+    }
 
+    @Test
+    fun testLongClickIntent_safetyCenterDisabled() {
         whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(false)
+        val cameraTile = CameraToggleTile(
+                host,
+                uiEventLogger,
+                testableLooper.looper,
+                Handler(testableLooper.looper),
+                metricsLogger,
+                FalsingManagerFake(),
+                statusBarStateController,
+                activityStarter,
+                qsLogger,
+                privacyController,
+                keyguardStateController,
+                safetyCenterManager)
         assertThat(tile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_SETTINGS)
+        cameraTile.destroy()
+        testableLooper.processAllMessages()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
index 0fcfdb6..b98a7570 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
@@ -123,11 +123,44 @@
     }
 
     @Test
-    fun testLongClickIntent() {
+    fun testLongClickIntent_safetyCenterEnabled() {
         whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(true)
-        assertThat(tile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS)
+        val micTile = MicrophoneToggleTile(
+                host,
+                uiEventLogger,
+                testableLooper.looper,
+                Handler(testableLooper.looper),
+                metricsLogger,
+                FalsingManagerFake(),
+                statusBarStateController,
+                activityStarter,
+                qsLogger,
+                privacyController,
+                keyguardStateController,
+                safetyCenterManager)
+        assertThat(micTile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS)
+        micTile.destroy()
+        testableLooper.processAllMessages()
+    }
 
+    @Test
+    fun testLongClickIntent_safetyCenterDisabled() {
         whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(false)
-        assertThat(tile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_SETTINGS)
+        val micTile = MicrophoneToggleTile(
+                host,
+                uiEventLogger,
+                testableLooper.looper,
+                Handler(testableLooper.looper),
+                metricsLogger,
+                FalsingManagerFake(),
+                statusBarStateController,
+                activityStarter,
+                qsLogger,
+                privacyController,
+                keyguardStateController,
+                safetyCenterManager)
+        assertThat(micTile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_SETTINGS)
+        micTile.destroy()
+        testableLooper.processAllMessages()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
index 5b3d45b..ac03073 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
@@ -22,11 +22,13 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.Dialog;
 import android.os.Handler;
 import android.service.quicksettings.Tile;
 import android.testing.AndroidTestingRunner;
@@ -35,11 +37,11 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -48,6 +50,7 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
 import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -89,6 +92,10 @@
     private PanelInteractor mPanelInteractor;
     @Mock
     private QsEventLogger mUiEventLogger;
+    @Mock
+    private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
+    @Mock
+    private Dialog mPermissionDialogPrompt;
 
     private TestableLooper mTestableLooper;
     private ScreenRecordTile mTile;
@@ -116,7 +123,8 @@
                 mKeyguardDismissUtil,
                 mKeyguardStateController,
                 mDialogLaunchAnimator,
-                mPanelInteractor
+                mPanelInteractor,
+                mMediaProjectionMetricsLogger
         );
 
         mTile.initialize();
@@ -280,4 +288,27 @@
         assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.qs_screen_record_icon_off));
     }
 
+    @Test
+    public void showingDialogPrompt_logsMediaProjectionPermissionRequested() {
+        when(mController.isStarting()).thenReturn(false);
+        when(mController.isRecording()).thenReturn(false);
+        when(mController.createScreenRecordDialog(any(), any(), any(), any(), any()))
+                .thenReturn(mPermissionDialogPrompt);
+
+        mTile.handleClick(null /* view */);
+        mTestableLooper.processAllMessages();
+
+        verify(mController).createScreenRecordDialog(any(), eq(mFeatureFlags),
+                eq(mDialogLaunchAnimator), eq(mActivityStarter), any());
+        var onDismissAction = ArgumentCaptor.forClass(ActivityStarter.OnDismissAction.class);
+        verify(mKeyguardDismissUtil).executeWhenUnlocked(
+                onDismissAction.capture(), anyBoolean(), anyBoolean());
+        assertNotNull(onDismissAction.getValue());
+
+        onDismissAction.getValue().onDismiss();
+
+        verify(mPermissionDialogPrompt).show();
+        verify(mMediaProjectionMetricsLogger).notifyPermissionRequestDisplayed();
+    }
+
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
index f1fcee3..9907278 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
@@ -23,11 +23,14 @@
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dump.LogcatEchoTrackerAlways
 import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import java.io.PrintWriter
 import java.io.StringWriter
@@ -42,6 +45,7 @@
 class QSTileLoggerTest : SysuiTestCase() {
 
     @Mock private lateinit var statusBarController: StatusBarStateController
+    @Mock private lateinit var logBufferFactory: LogBufferFactory
 
     private val chattyLogBuffer = LogBuffer("TestChatty", 5, LogcatEchoTrackerAlways())
     private val logBuffer = LogBuffer("Test", 1, LogcatEchoTrackerAlways())
@@ -51,10 +55,11 @@
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
+        whenever(logBufferFactory.create(any(), any(), any())).thenReturn(logBuffer)
         underTest =
             QSTileLogger(
                 mapOf(TileSpec.create("chatty_tile") to chattyLogBuffer),
-                { logBuffer },
+                logBufferFactory,
                 statusBarController
             )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 7b13de6..e1345d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -42,6 +42,7 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Assume.assumeTrue
@@ -292,6 +293,7 @@
                 initialSceneKey = SceneKey.Lockscreen,
                 authenticationMethod = AuthenticationMethodModel.Pin,
                 isDeviceUnlocked = false,
+                startsAwake = false
             )
             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
             underTest.start()
@@ -299,6 +301,7 @@
             utils.deviceEntryRepository.setUnlocked(true)
             runCurrent()
             powerInteractor.setAwakeForTest()
+            runCurrent()
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
         }
@@ -403,42 +406,43 @@
                 initialSceneKey = SceneKey.Lockscreen,
                 authenticationMethod = AuthenticationMethodModel.Pin,
                 isDeviceUnlocked = false,
+                startsAwake = false,
             )
             underTest.start()
             runCurrent()
             verify(falsingCollector, never()).onScreenTurningOn()
             verify(falsingCollector, never()).onScreenOnFromTouch()
-            verify(falsingCollector, never()).onScreenOff()
+            verify(falsingCollector, times(1)).onScreenOff()
 
             powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON)
             runCurrent()
             verify(falsingCollector, times(1)).onScreenTurningOn()
             verify(falsingCollector, never()).onScreenOnFromTouch()
-            verify(falsingCollector, never()).onScreenOff()
+            verify(falsingCollector, times(1)).onScreenOff()
 
             powerInteractor.setAsleepForTest()
             runCurrent()
             verify(falsingCollector, times(1)).onScreenTurningOn()
             verify(falsingCollector, never()).onScreenOnFromTouch()
-            verify(falsingCollector, times(1)).onScreenOff()
+            verify(falsingCollector, times(2)).onScreenOff()
 
             powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_TAP)
             runCurrent()
             verify(falsingCollector, times(1)).onScreenTurningOn()
             verify(falsingCollector, times(1)).onScreenOnFromTouch()
-            verify(falsingCollector, times(1)).onScreenOff()
+            verify(falsingCollector, times(2)).onScreenOff()
 
             powerInteractor.setAsleepForTest()
             runCurrent()
             verify(falsingCollector, times(1)).onScreenTurningOn()
             verify(falsingCollector, times(1)).onScreenOnFromTouch()
-            verify(falsingCollector, times(2)).onScreenOff()
+            verify(falsingCollector, times(3)).onScreenOff()
 
             powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON)
             runCurrent()
             verify(falsingCollector, times(2)).onScreenTurningOn()
             verify(falsingCollector, times(1)).onScreenOnFromTouch()
-            verify(falsingCollector, times(2)).onScreenOff()
+            verify(falsingCollector, times(3)).onScreenOff()
         }
 
     @Test
@@ -509,11 +513,12 @@
             verify(falsingCollector, times(2)).onBouncerHidden()
         }
 
-    private fun prepareState(
+    private fun TestScope.prepareState(
         isDeviceUnlocked: Boolean = false,
         isBypassEnabled: Boolean = false,
         initialSceneKey: SceneKey? = null,
         authenticationMethod: AuthenticationMethodModel? = null,
+        startsAwake: Boolean = true,
     ): MutableStateFlow<ObservableTransitionState> {
         assumeTrue(Flags.SCENE_CONTAINER_ENABLED)
         sceneContainerFlags.enabled = true
@@ -537,6 +542,13 @@
                 authenticationMethod != AuthenticationMethodModel.None
             )
         }
+        if (startsAwake) {
+            powerInteractor.setAwakeForTest()
+        } else {
+            powerInteractor.setAsleepForTest()
+        }
+        runCurrent()
+
         return transitionStateFlow
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index 90d2e78..c439cfe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -23,11 +23,13 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.Dialog;
 import android.app.PendingIntent;
+import android.content.Context;
 import android.content.Intent;
 import android.os.Looper;
 import android.testing.AndroidTestingRunner;
@@ -64,6 +66,8 @@
  */
 public class RecordingControllerTest extends SysuiTestCase {
 
+    private static final int TEST_USER_ID = 12345;
+
     private FakeSystemClock mFakeSystemClock = new FakeSystemClock();
     private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
     @Mock
@@ -91,6 +95,11 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        Context spiedContext = spy(mContext);
+        when(spiedContext.getUserId()).thenReturn(TEST_USER_ID);
+
+        when(mUserContextProvider.getUserContext()).thenReturn(spiedContext);
+
         mFeatureFlags = new FakeFeatureFlags();
         mController = new RecordingController(
                 mMainExecutor,
@@ -288,7 +297,6 @@
         if (Looper.myLooper() == null) {
             Looper.prepare();
         }
-
         mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
         mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
         when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false);
@@ -297,6 +305,8 @@
                 mDialogLaunchAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
 
         verify(mMediaProjectionMetricsLogger)
-                .notifyProjectionInitiated(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
+                .notifyProjectionInitiated(
+                        TEST_USER_ID,
+                        SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index eb00610..be82bc3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -90,7 +90,8 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
 import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
-import com.android.systemui.user.domain.interactor.UserInteractor;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
 
 import com.google.common.util.concurrent.MoreExecutors;
 
@@ -131,6 +132,7 @@
     @Mock private AuthController mAuthController;
     @Mock private ShadeExpansionStateManager mShadeExpansionStateManager;
     @Mock private ShadeWindowLogger mShadeWindowLogger;
+    @Mock private SelectedUserInteractor mSelectedUserInteractor;
     @Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters;
     @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListener;
     private final Executor mBackgroundExecutor = MoreExecutors.directExecutor();
@@ -216,6 +218,7 @@
                 keyguardInteractor,
                 featureFlags,
                 mKeyguardSecurityModel,
+                mSelectedUserInteractor,
                 powerInteractor);
 
         mShadeInteractor =
@@ -230,7 +233,7 @@
                         keyguardTransitionInteractor,
                         powerInteractor,
                         new FakeUserSetupRepository(),
-                        mock(UserInteractor.class),
+                        mock(UserSwitcherInteractor.class),
                         new SharedNotificationContainerInteractor(
                                 configurationRepository,
                                 mContext,
@@ -255,7 +258,8 @@
                 mAuthController,
                 mShadeExpansionStateManager,
                 () -> mShadeInteractor,
-                mShadeWindowLogger) {
+                mShadeWindowLogger,
+                () -> mSelectedUserInteractor) {
                     @Override
                     protected boolean isDebuggable() {
                         return false;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 677d9db..da49230 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -21,7 +21,9 @@
 import android.testing.TestableLooper.RunWithLooper
 import android.view.KeyEvent
 import android.view.MotionEvent
+import android.view.View
 import android.view.ViewGroup
+import androidx.core.view.contains
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardMessageAreaController
 import com.android.keyguard.KeyguardSecurityContainerController
@@ -29,6 +31,8 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.LockIconViewController
 import com.android.keyguard.dagger.KeyguardBouncerComponent
+import com.android.systemui.FakeFeatureFlagsImpl
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.back.domain.interactor.BackActionInteractor
 import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
@@ -43,11 +47,19 @@
 import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.classifier.FalsingCollectorFake
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.compose.ComposeFacade.isComposeAvailable
 import com.android.systemui.dock.DockManager
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.dump.logcatLogBuffer
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags.ALTERNATE_BOUNCER_VIEW
+import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
+import com.android.systemui.flags.Flags.MIGRATE_NSSL
+import com.android.systemui.flags.Flags.REVAMPED_BOUNCER_MESSAGES
+import com.android.systemui.flags.Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION
+import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON
+import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_FEATURES
 import com.android.systemui.flags.SystemPropertiesHelper
 import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler
 import com.android.systemui.keyguard.DismissCallbackRegistry
@@ -81,7 +93,9 @@
 import com.android.systemui.statusbar.window.StatusBarWindowStateController
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider
 import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -96,6 +110,7 @@
 import org.mockito.Mockito.anyFloat
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
+import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 import java.util.Optional
@@ -134,6 +149,7 @@
     @Mock
     private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener
     @Mock private lateinit var notificationInsetsController: NotificationInsetsController
+    @Mock private lateinit var mCommunalViewModel: CommunalViewModel
     @Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
     @Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
     @Mock lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController
@@ -142,6 +158,7 @@
         Optional<UnfoldTransitionProgressProvider>
     @Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     @Mock lateinit var dragDownHelper: DragDownHelper
+    @Mock lateinit var mSelectedUserInteractor: SelectedUserInteractor
     @Mock
     lateinit var primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel
     @Mock lateinit var sysUIKeyEventHandler: SysUIKeyEventHandler
@@ -157,7 +174,8 @@
 
     private lateinit var testScope: TestScope
 
-    private lateinit var featureFlags: FakeFeatureFlags
+    private lateinit var featureFlagsClassic: FakeFeatureFlagsClassic
+    private lateinit var featureFlags: FakeFeatureFlagsImpl
 
     @Before
     fun setUp() {
@@ -172,14 +190,16 @@
         whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition)
             .thenReturn(emptyFlow<TransitionStep>())
 
-        featureFlags = FakeFeatureFlags()
-        featureFlags.set(Flags.TRACKPAD_GESTURE_COMMON, true)
-        featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false)
-        featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
-        featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
-        featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
-        featureFlags.set(Flags.MIGRATE_NSSL, false)
-        featureFlags.set(Flags.ALTERNATE_BOUNCER_VIEW, false)
+        featureFlagsClassic = FakeFeatureFlagsClassic()
+        featureFlagsClassic.set(TRACKPAD_GESTURE_COMMON, true)
+        featureFlagsClassic.set(TRACKPAD_GESTURE_FEATURES, false)
+        featureFlagsClassic.set(SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
+        featureFlagsClassic.set(REVAMPED_BOUNCER_MESSAGES, true)
+        featureFlagsClassic.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
+        featureFlagsClassic.set(MIGRATE_NSSL, false)
+        featureFlagsClassic.set(ALTERNATE_BOUNCER_VIEW, false)
+
+        featureFlags = FakeFeatureFlagsImpl()
 
         testScope = TestScope()
         fakeClock = FakeSystemClock()
@@ -200,8 +220,6 @@
                 centralSurfaces,
                 dozeServiceHost,
                 dozeScrimController,
-                backActionInteractor,
-                powerInteractor,
                 notificationShadeWindowController,
                 unfoldTransitionProgressProvider,
                 keyguardUnlockAnimationController,
@@ -216,14 +234,16 @@
                 mock(KeyguardMessageAreaController.Factory::class.java),
                 keyguardTransitionInteractor,
                 primaryBouncerToGoneTransitionViewModel,
+                mCommunalViewModel,
                 notificationExpansionRepository,
+                featureFlagsClassic,
                 featureFlags,
                 fakeClock,
                 BouncerMessageInteractor(
                     repository = BouncerMessageRepositoryImpl(),
                     userRepository = FakeUserRepository(),
                     countDownTimerUtil = mock(CountDownTimerUtil::class.java),
-                    featureFlags = featureFlags,
+                    featureFlags = featureFlagsClassic,
                     updateMonitor = mock(KeyguardUpdateMonitor::class.java),
                     biometricSettingsRepository = FakeBiometricSettingsRepository(),
                     applicationScope = testScope.backgroundScope,
@@ -243,6 +263,7 @@
                             mock(KeyguardUpdateMonitor::class.java),
                             FakeTrustRepository(),
                             testScope.backgroundScope,
+                            mSelectedUserInteractor,
                         ),
                     facePropertyRepository = FakeFacePropertyRepository(),
                     deviceEntryFingerprintAuthRepository =
@@ -254,6 +275,7 @@
                 sysUIKeyEventHandler,
                 primaryBouncerInteractor,
                 alternateBouncerInteractor,
+                mSelectedUserInteractor,
             )
         underTest.setupExpandedStatusBar()
         underTest.setDragDownHelper(dragDownHelper)
@@ -441,7 +463,7 @@
         whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT))
                 .thenReturn(true)
 
-        featureFlags.set(Flags.MIGRATE_NSSL, true)
+        featureFlagsClassic.set(MIGRATE_NSSL, true)
 
         // THEN touch should NOT be intercepted by NotificationShade
         assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isFalse()
@@ -458,7 +480,7 @@
         whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT))
                 .thenReturn(false)
 
-        featureFlags.set(Flags.MIGRATE_NSSL, true)
+        featureFlagsClassic.set(MIGRATE_NSSL, true)
 
         // THEN touch should NOT be intercepted by NotificationShade
         assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue()
@@ -472,6 +494,48 @@
         }
 
     @Test
+    fun setsUpCommunalHubLayout_whenFlagEnabled() {
+        if (!isComposeAvailable()) {
+            return
+        }
+
+        featureFlags.setFlag(FLAG_COMMUNAL_HUB, true)
+
+        val mockCommunalPlaceholder = mock(View::class.java)
+        val fakeViewIndex = 20
+        whenever(view.findViewById<View>(R.id.communal_ui_stub)).thenReturn(mockCommunalPlaceholder)
+        whenever(view.indexOfChild(mockCommunalPlaceholder)).thenReturn(fakeViewIndex)
+        whenever(view.context).thenReturn(context)
+
+        underTest.setupCommunalHubLayout()
+
+        // Communal view added as a child of the container at the proper index, the stub is removed.
+        verify(view).removeView(mockCommunalPlaceholder)
+        verify(view).addView(any(), eq(fakeViewIndex))
+    }
+
+    @Test
+    fun doesNotSetupCommunalHubLayout_whenFlagDisabled() {
+        if (!isComposeAvailable()) {
+            return
+        }
+
+        featureFlags.setFlag(FLAG_COMMUNAL_HUB, false)
+
+        val mockCommunalPlaceholder = mock(View::class.java)
+        val fakeViewIndex = 20
+        whenever(view.findViewById<View>(R.id.communal_ui_stub)).thenReturn(mockCommunalPlaceholder)
+        whenever(view.indexOfChild(mockCommunalPlaceholder)).thenReturn(fakeViewIndex)
+        whenever(view.context).thenReturn(context)
+
+        underTest.setupCommunalHubLayout()
+
+        // No adding or removing of views occurs.
+        verify(view, times(0)).removeView(mockCommunalPlaceholder)
+        verify(view, times(0)).addView(any(), eq(fakeViewIndex))
+    }
+
+    @Test
     fun forwardsDispatchKeyEvent() {
         val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_B)
         interactionEventHandler.dispatchKeyEvent(keyEvent)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index a4a2ca0..c94741f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -28,6 +28,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.LockIconViewController
 import com.android.keyguard.dagger.KeyguardBouncerComponent
+import com.android.systemui.FakeFeatureFlagsImpl
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.back.domain.interactor.BackActionInteractor
 import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
@@ -42,6 +43,7 @@
 import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.classifier.FalsingCollectorFake
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.dock.DockManager
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.dump.logcatLogBuffer
@@ -79,6 +81,7 @@
 import com.android.systemui.statusbar.window.StatusBarWindowStateController
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider
 import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -141,9 +144,11 @@
     private lateinit var unfoldTransitionProgressProvider:
         Optional<UnfoldTransitionProgressProvider>
     @Mock private lateinit var notificationInsetsController: NotificationInsetsController
+    @Mock private lateinit var mCommunalViewModel: CommunalViewModel
     @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     @Mock lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
     @Mock lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
+    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
     @Mock
     private lateinit var primaryBouncerToGoneTransitionViewModel:
         PrimaryBouncerToGoneTransitionViewModel
@@ -202,8 +207,6 @@
                 centralSurfaces,
                 dozeServiceHost,
                 dozeScrimController,
-                backActionInteractor,
-                powerInteractor,
                 notificationShadeWindowController,
                 unfoldTransitionProgressProvider,
                 keyguardUnlockAnimationController,
@@ -218,8 +221,10 @@
                 Mockito.mock(KeyguardMessageAreaController.Factory::class.java),
                 keyguardTransitionInteractor,
                 primaryBouncerToGoneTransitionViewModel,
+                mCommunalViewModel,
                 NotificationExpansionRepository(),
                 featureFlags,
+                FakeFeatureFlagsImpl(),
                 FakeSystemClock(),
                 BouncerMessageInteractor(
                     repository = BouncerMessageRepositoryImpl(),
@@ -245,6 +250,7 @@
                             Mockito.mock(KeyguardUpdateMonitor::class.java),
                             FakeTrustRepository(),
                             testScope.backgroundScope,
+                            mSelectedUserInteractor,
                         ),
                     facePropertyRepository = FakeFacePropertyRepository(),
                     deviceEntryFingerprintAuthRepository =
@@ -256,6 +262,7 @@
                 Mockito.mock(SysUIKeyEventHandler::class.java),
                 primaryBouncerInteractor,
                 alternateBouncerInteractor,
+                mSelectedUserInteractor,
             )
 
         controller.setupExpandedStatusBar()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index 65174ba..0fcfaf9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -95,15 +95,17 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
 import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
-import com.android.systemui.user.domain.interactor.UserInteractor;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
 import com.android.systemui.util.kotlin.JavaAdapter;
 
+import dagger.Lazy;
+
 import org.junit.After;
 import org.junit.Before;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import dagger.Lazy;
 import kotlinx.coroutines.test.TestScope;
 
 public class QuickSettingsControllerBaseTest extends SysuiTestCase {
@@ -162,7 +164,8 @@
     @Mock protected DumpManager mDumpManager;
     @Mock protected UiEventLogger mUiEventLogger;
     @Mock protected CastController mCastController;
-    @Mock protected UserInteractor mUserInteractor;
+    @Mock protected UserSwitcherInteractor mUserSwitcherInteractor;
+    @Mock protected SelectedUserInteractor mSelectedUserInteractor;
     protected FakeDisableFlagsRepository mDisableFlagsRepository =
             new FakeDisableFlagsRepository();
     protected FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
@@ -249,6 +252,7 @@
                 keyguardInteractor,
                 featureFlags,
                 mock(KeyguardSecurityModel.class),
+                mSelectedUserInteractor,
                 powerInteractor);
 
         ResourcesSplitShadeStateController splitShadeStateController =
@@ -266,7 +270,7 @@
                         keyguardTransitionInteractor,
                         powerInteractor,
                         new FakeUserSetupRepository(),
-                        mUserInteractor,
+                        mUserSwitcherInteractor,
                         new SharedNotificationContainerInteractor(
                                 configurationRepository,
                                 mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
index 5ca45f3..7cb6d93 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
@@ -398,6 +398,12 @@
                 .isEqualTo(mQsController.getScrimCornerRadius());
     }
 
+    @Test
+    public void disallowTouches_nullQs_false() {
+        mQsController.setQs(null);
+        assertThat(mQsController.disallowTouches()).isFalse();
+    }
+
     private void lockScreen() {
         mQsController.setBarState(KEYGUARD);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
index 81382a4..3a260ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
@@ -445,105 +445,6 @@
         }
 
     @Test
-    fun expanding_shadeDraggedDown_expandingTrue() =
-        testScope.runTest() {
-            val actual by collectLastValue(underTest.isAnyExpanding)
-
-            // GIVEN shade and QS collapsed
-            shadeRepository.setLegacyShadeExpansion(0f)
-            shadeRepository.setQsExpansion(0f)
-            runCurrent()
-
-            // WHEN shade partially expanded
-            shadeRepository.setLegacyShadeExpansion(.5f)
-            runCurrent()
-
-            // THEN anyExpanding is true
-            assertThat(actual).isTrue()
-        }
-
-    @Test
-    fun expanding_qsDraggedDown_expandingTrue() =
-        testScope.runTest() {
-            val actual by collectLastValue(underTest.isAnyExpanding)
-
-            // GIVEN shade and QS collapsed
-            shadeRepository.setLegacyShadeExpansion(0f)
-            shadeRepository.setQsExpansion(0f)
-            runCurrent()
-
-            // WHEN shade partially expanded
-            shadeRepository.setQsExpansion(.5f)
-            runCurrent()
-
-            // THEN anyExpanding is true
-            assertThat(actual).isTrue()
-        }
-
-    @Test
-    fun expanding_shadeDraggedUpAndDown() =
-        testScope.runTest() {
-            val actual by collectLastValue(underTest.isAnyExpanding)
-
-            // WHEN shade starts collapsed then partially expanded
-            shadeRepository.setLegacyShadeExpansion(0f)
-            shadeRepository.setLegacyShadeExpansion(.5f)
-            shadeRepository.setQsExpansion(0f)
-            runCurrent()
-
-            // THEN anyExpanding is true
-            assertThat(actual).isTrue()
-
-            // WHEN shade dragged up a bit
-            shadeRepository.setLegacyShadeExpansion(.2f)
-            runCurrent()
-
-            // THEN anyExpanding is still true
-            assertThat(actual).isTrue()
-
-            // WHEN shade dragged down a bit
-            shadeRepository.setLegacyShadeExpansion(.7f)
-            runCurrent()
-
-            // THEN anyExpanding is still true
-            assertThat(actual).isTrue()
-
-            // WHEN shade fully expanded
-            shadeRepository.setLegacyShadeExpansion(1f)
-            runCurrent()
-
-            // THEN anyExpanding is now false
-            assertThat(actual).isFalse()
-
-            // WHEN shade dragged up a bit
-            shadeRepository.setLegacyShadeExpansion(.7f)
-            runCurrent()
-
-            // THEN anyExpanding is still false
-            assertThat(actual).isFalse()
-        }
-
-    @Test
-    fun expanding_shadeDraggedDownThenUp_expandingFalse() =
-        testScope.runTest() {
-            val actual by collectLastValue(underTest.isAnyExpanding)
-
-            // GIVEN shade starts collapsed
-            shadeRepository.setLegacyShadeExpansion(0f)
-            shadeRepository.setQsExpansion(0f)
-            runCurrent()
-
-            // WHEN shade expands but doesn't complete
-            shadeRepository.setLegacyShadeExpansion(.5f)
-            runCurrent()
-            shadeRepository.setLegacyShadeExpansion(0f)
-            runCurrent()
-
-            // THEN anyExpanding is false
-            assertThat(actual).isFalse()
-        }
-
-    @Test
     fun lockscreenShadeExpansion_idle_onScene() =
         testScope.runTest() {
             // GIVEN an expansion flow based on transitions to and from a scene
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index a5f5fc7..43adc69 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -22,14 +22,18 @@
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
+import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
 import static android.os.UserHandle.USER_ALL;
 import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
 import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
 
+import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertTrue;
 
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -99,6 +103,7 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
+    private static final int TEST_PROFILE_USERHANDLE = 12;
     @Mock
     private NotificationPresenter mPresenter;
     @Mock
@@ -701,6 +706,60 @@
         assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(newUserId));
     }
 
+    @Test
+    public void testProfileAvailabilityIntent() {
+        mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+        mLockscreenUserManager.mCurrentProfiles.clear();
+        assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size());
+        mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
+        simulateProfileAvailabilityActions(Intent.ACTION_PROFILE_AVAILABLE);
+        assertEquals(2, mLockscreenUserManager.mCurrentProfiles.size());
+    }
+
+    @Test
+    public void testProfileUnAvailabilityIntent() {
+        mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+        mLockscreenUserManager.mCurrentProfiles.clear();
+        assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size());
+        mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
+        simulateProfileAvailabilityActions(Intent.ACTION_PROFILE_UNAVAILABLE);
+        assertEquals(2, mLockscreenUserManager.mCurrentProfiles.size());
+    }
+
+    @Test
+    public void testManagedProfileAvailabilityIntent() {
+        mSetFlagsRule.disableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+        mLockscreenUserManager.mCurrentProfiles.clear();
+        mLockscreenUserManager.mCurrentManagedProfiles.clear();
+        assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size());
+        assertEquals(0, mLockscreenUserManager.mCurrentManagedProfiles.size());
+        mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
+        simulateProfileAvailabilityActions(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
+        assertEquals(2, mLockscreenUserManager.mCurrentProfiles.size());
+        assertEquals(1, mLockscreenUserManager.mCurrentManagedProfiles.size());
+    }
+
+    @Test
+    public void testManagedProfileUnAvailabilityIntent() {
+        mSetFlagsRule.disableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+        mLockscreenUserManager.mCurrentProfiles.clear();
+        mLockscreenUserManager.mCurrentManagedProfiles.clear();
+        assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size());
+        assertEquals(0, mLockscreenUserManager.mCurrentManagedProfiles.size());
+        mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
+        simulateProfileAvailabilityActions(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
+        assertEquals(2, mLockscreenUserManager.mCurrentProfiles.size());
+        assertEquals(1, mLockscreenUserManager.mCurrentManagedProfiles.size());
+    }
+
+    private void simulateProfileAvailabilityActions(String intentAction) {
+        BroadcastReceiver broadcastReceiver =
+                mLockscreenUserManager.getBaseBroadcastReceiverForTest();
+        final Intent intent = new Intent(intentAction);
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, TEST_PROFILE_USERHANDLE);
+        broadcastReceiver.onReceive(mContext, intent);
+    }
+
     private class TestNotificationLockscreenUserManager
             extends NotificationLockscreenUserManagerImpl {
         public TestNotificationLockscreenUserManager(Context context) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt
deleted file mode 100644
index cfcf425..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar
-
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.util.mockito.whenever
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.anyBoolean
-import org.mockito.Mockito.doCallRealMethod
-import org.mockito.Mockito.doReturn
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-/**
- * Temporary test for the lock screen live wallpaper project.
- *
- * TODO(b/273443374): remove this test
- */
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-class NotificationMediaManagerTest : SysuiTestCase() {
-
-    @Mock private lateinit var notificationMediaManager: NotificationMediaManager
-
-    @Mock private lateinit var mockBackDropView: BackDropView
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        doCallRealMethod().whenever(notificationMediaManager).updateMediaMetaData(anyBoolean())
-        doReturn(mockBackDropView).whenever(notificationMediaManager).backDropView
-    }
-
-    @After fun tearDown() {}
-
-    /** Check that updateMediaMetaData is a no-op with mIsLockscreenLiveWallpaperEnabled = true */
-    @Test
-    fun testUpdateMediaMetaDataDisabled() {
-        notificationMediaManager.mIsLockscreenLiveWallpaperEnabled = true
-        for (metaDataChanged in listOf(true, false)) {
-            for (allowEnterAnimation in listOf(true, false)) {
-                notificationMediaManager.updateMediaMetaData(metaDataChanged)
-                verify(notificationMediaManager, never()).mediaMetadata
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt
index 8397702..df8afde 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt
@@ -26,6 +26,7 @@
     override var forceVisible: Boolean = false,
     override val showAnimation: Boolean = true,
     override var contentDescription: String? = "",
+    override val shouldAnnounceAccessibilityEvent: Boolean = false
 ) : StatusEvent
 
 class FakePrivacyStatusEvent(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
index 4fcccf8..fee8b82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -33,6 +33,8 @@
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
 import com.android.systemui.statusbar.window.StatusBarWindowController
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import junit.framework.Assert.assertEquals
@@ -370,6 +372,63 @@
     }
 
     @Test
+    fun testAccessibilityAnnouncement_announced() = runTest {
+        // Instantiate class under test with TestScope from runTest
+        initializeSystemStatusAnimationScheduler(testScope = this)
+        val accessibilityDesc = "Some desc"
+        val mockView = mock<View>()
+        val mockAnimatableView =
+            mock<BackgroundAnimatableView> { whenever(view).thenReturn(mockView) }
+
+        scheduleFakeEventWithView(
+            accessibilityDesc,
+            mockAnimatableView,
+            shouldAnnounceAccessibilityEvent = true
+        )
+        fastForwardAnimationToState(ANIMATING_OUT)
+
+        verify(mockView).announceForAccessibility(eq(accessibilityDesc))
+    }
+
+    @Test
+    fun testAccessibilityAnnouncement_nullDesc_noAnnouncement() = runTest {
+        // Instantiate class under test with TestScope from runTest
+        initializeSystemStatusAnimationScheduler(testScope = this)
+        val accessibilityDesc = null
+        val mockView = mock<View>()
+        val mockAnimatableView =
+            mock<BackgroundAnimatableView> { whenever(view).thenReturn(mockView) }
+
+        scheduleFakeEventWithView(
+            accessibilityDesc,
+            mockAnimatableView,
+            shouldAnnounceAccessibilityEvent = true
+        )
+        fastForwardAnimationToState(ANIMATING_OUT)
+
+        verify(mockView, never()).announceForAccessibility(any())
+    }
+
+    @Test
+    fun testAccessibilityAnnouncement_notNeeded_noAnnouncement() = runTest {
+        // Instantiate class under test with TestScope from runTest
+        initializeSystemStatusAnimationScheduler(testScope = this)
+        val accessibilityDesc = "something"
+        val mockView = mock<View>()
+        val mockAnimatableView =
+            mock<BackgroundAnimatableView> { whenever(view).thenReturn(mockView) }
+
+        scheduleFakeEventWithView(
+            accessibilityDesc,
+            mockAnimatableView,
+            shouldAnnounceAccessibilityEvent = false
+        )
+        fastForwardAnimationToState(ANIMATING_OUT)
+
+        verify(mockView, never()).announceForAccessibility(any())
+    }
+
+    @Test
     fun testPrivacyDot_isRemovedDuringChipAnimation() = runTest {
         // Instantiate class under test with TestScope from runTest
         initializeSystemStatusAnimationScheduler(testScope = this)
@@ -572,6 +631,20 @@
         return privacyChip
     }
 
+    private fun scheduleFakeEventWithView(
+        desc: String?,
+        view: BackgroundAnimatableView,
+        shouldAnnounceAccessibilityEvent: Boolean
+    ) {
+        val fakeEvent =
+            FakeStatusEvent(
+                viewCreator = { view },
+                contentDescription = desc,
+                shouldAnnounceAccessibilityEvent = shouldAnnounceAccessibilityEvent
+            )
+        systemStatusAnimationScheduler.onStatusEvent(fakeEvent)
+    }
+
     private fun createAndScheduleFakeBatteryEvent(): BatteryStatusChip {
         val batteryChip = BatteryStatusChip(mContext)
         val fakeBatteryEvent =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
index c664c39..2ef4374 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
@@ -18,10 +18,16 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 
+import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -33,8 +39,8 @@
 import android.view.animation.Interpolator;
 
 import com.android.app.animation.Interpolators;
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.stack.AnimationFilter;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -85,7 +91,7 @@
             return mEffectiveProperty;
         }
     };
-    private AnimatorListenerAdapter mFinishListener = mock(AnimatorListenerAdapter.class);
+    private AnimatorListenerAdapter mFinishListener;
     private AnimationProperties mAnimationProperties = new AnimationProperties() {
         @Override
         public AnimationFilter getAnimationFilter() {
@@ -104,6 +110,7 @@
     @Before
     public void setUp() {
         mView = new View(getContext());
+        mFinishListener = mock(AnimatorListenerAdapter.class);
     }
 
     @Test
@@ -229,6 +236,32 @@
     }
 
     @Test
+    public void testListenerCallbackOrderAndTagState() {
+        mAnimationFilter.reset();
+        mAnimationFilter.animate(mProperty.getProperty());
+        mAnimationProperties.setCustomInterpolator(mEffectiveProperty, mTestInterpolator);
+        mAnimationProperties.setDuration(500);
+
+        // Validates that the onAnimationEnd function set by PropertyAnimator was run first.
+        doAnswer(invocation -> {
+            assertNull(mView.getTag(mProperty.getAnimatorTag()));
+            return null;
+        })
+                .when(mFinishListener)
+                .onAnimationEnd(any(Animator.class), anyBoolean());
+
+        // Begin the animation and verify it set state correctly
+        PropertyAnimator.startAnimation(mView, mProperty, 200f, mAnimationProperties);
+        ValueAnimator animator = ViewState.getChildTag(mView, mProperty.getAnimatorTag());
+        assertNotNull(animator);
+        assertNotNull(mView.getTag(mProperty.getAnimatorTag()));
+
+        // Terminate the animation to run end runners, and validate they executed.
+        animator.end();
+        verify(mFinishListener).onAnimationEnd(animator, false);
+    }
+
+    @Test
     public void testIsAnimating() {
         mAnimationFilter.reset();
         mAnimationFilter.animate(mProperty.getProperty());
@@ -236,4 +269,4 @@
         PropertyAnimator.startAnimation(mView, mProperty, 200f, mAnimationProperties);
         assertTrue(PropertyAnimator.isAnimating(mView, mProperty));
     }
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
index 27be4c8..df547ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
@@ -53,6 +54,7 @@
     val keyguardUpdateMonitor: KeyguardUpdateMonitor = mock()
     val statusBarStateController: StatusBarStateController = mock()
     val keyguardStateController: KeyguardStateController = mock()
+    val mSelectedUserInteractor: SelectedUserInteractor = mock()
 
     val coordinator: SensitiveContentCoordinator =
         DaggerTestSensitiveContentCoordinatorComponent
@@ -62,7 +64,8 @@
                         lockscreenUserManager,
                         keyguardUpdateMonitor,
                         statusBarStateController,
-                        keyguardStateController)
+                        keyguardStateController,
+                        mSelectedUserInteractor)
                 .coordinator
 
     @Test
@@ -263,7 +266,8 @@
             @BindsInstance lockscreenUserManager: NotificationLockscreenUserManager,
             @BindsInstance keyguardUpdateMonitor: KeyguardUpdateMonitor,
             @BindsInstance statusBarStateController: StatusBarStateController,
-            @BindsInstance keyguardStateController: KeyguardStateController
+            @BindsInstance keyguardStateController: KeyguardStateController,
+            @BindsInstance selectedUserInteractor: SelectedUserInteractor,
         ): TestSensitiveContentCoordinatorComponent
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index ac11ff2..0a7dc4e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -192,10 +192,26 @@
             val isOnLockscreen by collectLastValue(underTest.isOnLockscreen)
 
             keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(to = KeyguardState.GONE, transitionState = TransitionState.FINISHED)
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED
+                )
             )
             assertThat(isOnLockscreen).isFalse()
 
+            // While progressing from lockscreen, should still be true
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                    value = 0.8f,
+                    transitionState = TransitionState.RUNNING
+                )
+            )
+            assertThat(isOnLockscreen).isTrue()
+
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
                     to = KeyguardState.LOCKSCREEN,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 8344cd1..cfd220b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -52,14 +52,15 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.domain.interactor.BiometricUnlockInteractor;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
@@ -109,8 +110,6 @@
     @Mock
     private WakefulnessLifecycle mWakefulnessLifecycle;
     @Mock
-    private ScreenLifecycle mScreenLifecycle;
-    @Mock
     private StatusBarStateController mStatusBarStateController;
     @Mock
     private SessionTracker mSessionTracker;
@@ -126,6 +125,10 @@
     private ViewRootImpl mViewRootImpl;
     @Mock
     private DeviceEntryHapticsInteractor mDeviceEntryHapticsInteractor;
+    @Mock
+    private SelectedUserInteractor mSelectedUserInteractor;
+    @Mock
+    private BiometricUnlockInteractor mBiometricUnlockInteractor;
     private final FakeSystemClock mSystemClock = new FakeSystemClock();
     private FakeFeatureFlags mFeatureFlags;
     private BiometricUnlockController mBiometricUnlockController;
@@ -163,7 +166,9 @@
                 mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper,
                 mSystemClock,
                 mFeatureFlags,
-                mDeviceEntryHapticsInteractor
+                mDeviceEntryHapticsInteractor,
+                () -> mSelectedUserInteractor,
+                mBiometricUnlockInteractor
         );
         biometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
         biometricUnlockController.addListener(mBiometricUnlockEventsListener);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index c8cbe42..a59cd87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -277,8 +277,6 @@
             mNotificationShadeWindowViewControllerLazy;
     @Mock private NotificationShelfController mNotificationShelfController;
     @Mock private DozeParameters mDozeParameters;
-    @Mock private Lazy<LockscreenWallpaper> mLockscreenWallpaperLazy;
-    @Mock private LockscreenWallpaper mLockscreenWallpaper;
     @Mock private DozeServiceHost mDozeServiceHost;
     @Mock private BackActionInteractor mBackActionInteractor;
     @Mock private ViewMediatorCallback mKeyguardVieMediatorCallback;
@@ -404,7 +402,6 @@
         when(mGradientColors.supportsDarkText()).thenReturn(true);
         when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
 
-        when(mLockscreenWallpaperLazy.get()).thenReturn(mLockscreenWallpaper);
         when(mBiometricUnlockControllerLazy.get()).thenReturn(mBiometricUnlockController);
         when(mCameraLauncherLazy.get()).thenReturn(mCameraLauncher);
         when(mNotificationShadeWindowViewControllerLazy.get())
@@ -508,7 +505,6 @@
                 new NotificationExpansionRepository(),
                 mDozeParameters,
                 mScrimController,
-                mLockscreenWallpaperLazy,
                 mBiometricUnlockControllerLazy,
                 mAuthRippleController,
                 mDozeServiceHost,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index f5b7ca8..6fecbb0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -23,7 +23,6 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -44,6 +43,7 @@
 import com.android.systemui.doze.AlwaysOnDisplayPolicy;
 import com.android.systemui.doze.DozeScreenState;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.keyguard.domain.interactor.DozeInteractor;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -85,6 +85,7 @@
     @Mock private StatusBarStateController mStatusBarStateController;
     @Mock private ConfigurationController mConfigurationController;
     @Mock private UserTracker mUserTracker;
+    @Mock private DozeInteractor mDozeInteractor;
     @Captor private ArgumentCaptor<BatteryStateChangeCallback> mBatteryStateChangeCallback;
 
     /**
@@ -128,7 +129,8 @@
             mKeyguardUpdateMonitor,
             mConfigurationController,
             mStatusBarStateController,
-            mUserTracker
+            mUserTracker,
+            mDozeInteractor
         );
 
         verify(mBatteryController).addCallback(mBatteryStateChangeCallback.capture());
@@ -186,9 +188,7 @@
 
     @Test
     public void testGetAlwaysOn_whenBatterySaverCallback() {
-        DozeParameters.Callback callback = mock(DozeParameters.Callback.class);
-        mDozeParameters.addCallback(callback);
-
+        reset(mDozeInteractor);
         when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
         when(mBatteryController.isAodPowerSave()).thenReturn(true);
 
@@ -196,16 +196,16 @@
         mDozeParameters.onTuningChanged(Settings.Secure.DOZE_ALWAYS_ON, "1");
         mBatteryStateChangeCallback.getValue().onPowerSaveChanged(true);
 
-        verify(callback, times(2)).onAlwaysOnChange();
+        verify(mDozeInteractor, times(2)).setAodAvailable(anyBoolean());
         verify(mScreenOffAnimationController, times(2)).onAlwaysOnChanged(false);
         assertThat(mDozeParameters.getAlwaysOn()).isFalse();
 
         reset(mScreenOffAnimationController);
-        reset(callback);
+        reset(mDozeInteractor);
         when(mBatteryController.isAodPowerSave()).thenReturn(false);
         mBatteryStateChangeCallback.getValue().onPowerSaveChanged(true);
 
-        verify(callback).onAlwaysOnChange();
+        verify(mDozeInteractor).setAodAvailable(anyBoolean());
         verify(mScreenOffAnimationController).onAlwaysOnChanged(true);
         assertThat(mDozeParameters.getAlwaysOn()).isTrue();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt
deleted file mode 100644
index 47671fb..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.phone
-
-import android.app.WallpaperManager
-import android.content.pm.UserInfo
-import android.os.Looper
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.user.data.model.SelectionStatus
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.util.kotlin.JavaAdapter
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
-import com.android.systemui.utils.os.FakeHandler
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mockito.verify
-
-@SmallTest
-@OptIn(ExperimentalCoroutinesApi::class)
-class LockscreenWallpaperTest : SysuiTestCase() {
-
-    private lateinit var underTest: LockscreenWallpaper
-
-    private val testScope = TestScope(StandardTestDispatcher())
-    private val userRepository = FakeUserRepository()
-
-    private val wallpaperManager: WallpaperManager = mock()
-
-    @Before
-    fun setUp() {
-        whenever(wallpaperManager.isLockscreenLiveWallpaperEnabled).thenReturn(false)
-        whenever(wallpaperManager.isWallpaperSupported).thenReturn(true)
-        underTest =
-            LockscreenWallpaper(
-                /* wallpaperManager= */ wallpaperManager,
-                /* iWallpaperManager= */ mock(),
-                /* keyguardUpdateMonitor= */ mock(),
-                /* dumpManager= */ mock(),
-                /* mediaManager= */ mock(),
-                /* mainHandler= */ FakeHandler(Looper.getMainLooper()),
-                /* javaAdapter= */ JavaAdapter(testScope.backgroundScope),
-                /* userRepository= */ userRepository,
-                /* userTracker= */ mock(),
-            )
-        underTest.start()
-    }
-
-    @Test
-    fun getBitmap_matchesUserIdFromUserRepo() =
-        testScope.runTest {
-            val info = UserInfo(/* id= */ 5, /* name= */ "id5", /* flags= */ 0)
-            userRepository.setUserInfos(listOf(info))
-            userRepository.setSelectedUserInfo(info)
-
-            underTest.bitmap
-
-            verify(wallpaperManager).getWallpaperFile(any(), eq(5))
-        }
-
-    @Test
-    fun getBitmap_usesOldUserIfNewUserInProgress() =
-        testScope.runTest {
-            val info5 = UserInfo(/* id= */ 5, /* name= */ "id5", /* flags= */ 0)
-            val info6 = UserInfo(/* id= */ 6, /* name= */ "id6", /* flags= */ 0)
-            userRepository.setUserInfos(listOf(info5, info6))
-            userRepository.setSelectedUserInfo(info5)
-
-            // WHEN the selection of user 6 is only in progress
-            userRepository.setSelectedUserInfo(
-                info6,
-                selectionStatus = SelectionStatus.SELECTION_IN_PROGRESS
-            )
-
-            underTest.bitmap
-
-            // THEN we still use user 5 for wallpaper selection
-            verify(wallpaperManager).getWallpaperFile(any(), eq(5))
-        }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 6b3bd22..15c09b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -70,6 +70,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState;
 import com.android.systemui.keyguard.shared.model.TransitionState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
 import com.android.systemui.scrim.ScrimView;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
@@ -141,6 +142,8 @@
     @Mock private ScreenOffAnimationController mScreenOffAnimationController;
     @Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     @Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
+    @Mock private AlternateBouncerToGoneTransitionViewModel
+            mAlternateBouncerToGoneTransitionViewModel;
     @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     private final FakeWallpaperRepository mWallpaperRepository = new FakeWallpaperRepository();
     @Mock private CoroutineDispatcher mMainDispatcher;
@@ -264,10 +267,12 @@
         when(mDelayedWakeLockBuilder.build()).thenReturn(mWakeLock);
         when(mDockManager.isDocked()).thenReturn(false);
 
-        when(mKeyguardTransitionInteractor.getPrimaryBouncerToGoneTransition())
+        when(mKeyguardTransitionInteractor.transition(any(), any()))
                 .thenReturn(emptyFlow());
         when(mPrimaryBouncerToGoneTransitionViewModel.getScrimAlpha())
                 .thenReturn(emptyFlow());
+        when(mAlternateBouncerToGoneTransitionViewModel.getScrimAlpha())
+                .thenReturn(emptyFlow());
 
         mScrimController = new ScrimController(
                 mLightBarController,
@@ -285,6 +290,7 @@
                 mKeyguardUnlockAnimationController,
                 mStatusBarKeyguardViewManager,
                 mPrimaryBouncerToGoneTransitionViewModel,
+                mAlternateBouncerToGoneTransitionViewModel,
                 mKeyguardTransitionInteractor,
                 mWallpaperRepository,
                 mMainDispatcher,
@@ -992,6 +998,7 @@
                 mKeyguardUnlockAnimationController,
                 mStatusBarKeyguardViewManager,
                 mPrimaryBouncerToGoneTransitionViewModel,
+                mAlternateBouncerToGoneTransitionViewModel,
                 mKeyguardTransitionInteractor,
                 mWallpaperRepository,
                 mMainDispatcher,
@@ -1775,7 +1782,7 @@
     @Test
     public void ignoreTransitionRequestWhileKeyguardTransitionRunning() {
         mScrimController.transitionTo(ScrimState.UNLOCKED);
-        mScrimController.mPrimaryBouncerToGoneTransition.accept(
+        mScrimController.mBouncerToGoneTransition.accept(
                 new TransitionStep(KeyguardState.PRIMARY_BOUNCER, KeyguardState.GONE, 0f,
                         TransitionState.RUNNING, "ScrimControllerTest"));
 
@@ -1787,7 +1794,7 @@
     @Test
     public void primaryBouncerToGoneOnFinishCallsKeyguardFadedAway() {
         when(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(true);
-        mScrimController.mPrimaryBouncerToGoneTransition.accept(
+        mScrimController.mBouncerToGoneTransition.accept(
                 new TransitionStep(KeyguardState.PRIMARY_BOUNCER, KeyguardState.GONE, 0f,
                         TransitionState.FINISHED, "ScrimControllerTest"));
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 45e9224..46b3996 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -93,6 +93,7 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 import com.google.common.truth.Truth;
 
@@ -147,6 +148,7 @@
     @Mock private WindowInsetsController mWindowInsetsController;
     @Mock private TaskbarDelegate mTaskbarDelegate;
     @Mock private StatusBarKeyguardViewManager.KeyguardViewManagerCallback mCallback;
+    @Mock private SelectedUserInteractor mSelectedUserInteractor;
 
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback
@@ -212,7 +214,8 @@
                         mock(KeyguardTransitionInteractor.class),
                         StandardTestDispatcher(null, null),
                         () -> mock(WindowManagerLockscreenVisibilityInteractor.class),
-                        () -> mock(KeyguardDismissActionInteractor.class)) {
+                        () -> mock(KeyguardDismissActionInteractor.class),
+                        mSelectedUserInteractor) {
                     @Override
                     public ViewRootImpl getViewRootImpl() {
                         return mViewRootImpl;
@@ -715,7 +718,8 @@
                         mock(KeyguardTransitionInteractor.class),
                         StandardTestDispatcher(null, null),
                         () -> mock(WindowManagerLockscreenVisibilityInteractor.class),
-                        () -> mock(KeyguardDismissActionInteractor.class)) {
+                        () -> mock(KeyguardDismissActionInteractor.class),
+                        mSelectedUserInteractor) {
                     @Override
                     public ViewRootImpl getViewRootImpl() {
                         return mViewRootImpl;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
new file mode 100644
index 0000000..21d22bc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.statusbar.phone
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import kotlin.test.Test
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+class SystemUIBottomSheetDialogTest : SysuiTestCase() {
+
+    private val configurationController = mock<ConfigurationController>()
+
+    private lateinit var dialog: SystemUIBottomSheetDialog
+
+    @Before
+    fun setup() {
+        dialog = SystemUIBottomSheetDialog(mContext, configurationController)
+    }
+
+    @Test
+    fun onStart_registersConfigCallback() {
+        dialog.show()
+
+        verify(configurationController).addCallback(any())
+    }
+
+    @Test
+    fun onStop_unregisterConfigCallback() {
+        dialog.show()
+        dialog.dismiss()
+
+        verify(configurationController).removeCallback(any())
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
index c2f5665..3126362 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
@@ -201,11 +201,11 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.isWifiDefault)
 
-            val wifiEntry =
+            val mergedEntry =
                 mock<MergedCarrierEntry>().apply {
                     whenever(this.isDefaultNetwork).thenReturn(true)
                 }
-            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
             getCallback().onWifiEntriesChanged()
 
             assertThat(latest).isTrue()
@@ -229,11 +229,11 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.isWifiDefault)
 
-            val wifiEntry =
+            val mergedEntry =
                 mock<MergedCarrierEntry>().apply {
                     whenever(this.isDefaultNetwork).thenReturn(false)
                 }
-            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
             getCallback().onWifiEntriesChanged()
 
             assertThat(latest).isFalse()
@@ -526,13 +526,14 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.wifiNetwork)
 
-            val wifiEntry =
+            val mergedEntry =
                 mock<MergedCarrierEntry>().apply {
                     whenever(this.isPrimaryNetwork).thenReturn(true)
                     whenever(this.level).thenReturn(3)
                     whenever(this.subscriptionId).thenReturn(567)
+                    whenever(this.isDefaultNetwork).thenReturn(true)
                 }
-            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
             getCallback().onWifiEntriesChanged()
 
             assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
@@ -546,11 +547,12 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.wifiNetwork)
 
-            val wifiEntry =
+            val mergedEntry =
                 mock<MergedCarrierEntry>().apply {
                     whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.isDefaultNetwork).thenReturn(true)
                 }
-            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
             whenever(wifiManager.maxSignalLevel).thenReturn(5)
 
             getCallback().onWifiEntriesChanged()
@@ -566,12 +568,13 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.wifiNetwork)
 
-            val wifiEntry =
+            val mergedEntry =
                 mock<MergedCarrierEntry>().apply {
                     whenever(this.isPrimaryNetwork).thenReturn(true)
                     whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID)
+                    whenever(this.isDefaultNetwork).thenReturn(true)
                 }
-            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
 
             getCallback().onWifiEntriesChanged()
 
@@ -628,11 +631,12 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.wifiNetwork)
 
-            val wifiEntry =
+            val mergedEntry =
                 mock<MergedCarrierEntry>().apply {
                     whenever(this.isPrimaryNetwork).thenReturn(false)
                 }
-            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
             getCallback().onWifiEntriesChanged()
 
             assertThat(latest).isEqualTo(WifiNetworkModel.Inactive)
@@ -717,12 +721,14 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.wifiNetwork)
 
-            val wifiEntry =
+            val mergedEntry =
                 mock<MergedCarrierEntry>().apply {
                     whenever(this.isPrimaryNetwork).thenReturn(true)
                     whenever(this.level).thenReturn(3)
+                    whenever(this.isDefaultNetwork).thenReturn(true)
                 }
-            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
+            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
             getCallback().onWifiEntriesChanged()
 
             assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
@@ -730,6 +736,7 @@
 
             // WHEN we lose our current network
             whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
+            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(null)
             getCallback().onWifiEntriesChanged()
 
             // THEN we update to no network
@@ -767,6 +774,56 @@
         }
 
     @Test
+    fun wifiNetwork_carrierMerged_default_usesCarrierMergedInfo() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val mergedEntry =
+                mock<MergedCarrierEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(3)
+                    whenever(this.isDefaultNetwork).thenReturn(true)
+                }
+            val wifiEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(1)
+                    whenever(this.title).thenReturn(TITLE)
+                }
+            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
+        }
+
+    @Test
+    fun wifiNetwork_carrierMerged_notDefault_usesConnectedInfo() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val mergedEntry =
+                mock<MergedCarrierEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(3)
+                    whenever(this.isDefaultNetwork).thenReturn(false)
+                }
+            val wifiEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(1)
+                    whenever(this.title).thenReturn(TITLE)
+                }
+            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest is WifiNetworkModel.Active).isTrue()
+        }
+
+    @Test
     fun secondaryNetworks_activeEntriesEmpty_isEmpty() =
         testScope.runTest {
             featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
index cae892f..e6b09e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
@@ -38,6 +38,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 import dagger.Lazy;
 
@@ -67,6 +68,8 @@
     @Mock
     private Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
     @Mock
+    private SelectedUserInteractor mSelectedUserInteractor;
+    @Mock
     private KeyguardUpdateMonitorLogger mLogger;
     @Mock
     private FeatureFlags mFeatureFlags;
@@ -84,7 +87,8 @@
                 mKeyguardUnlockAnimationControllerLazy,
                 mLogger,
                 mDumpManager,
-                mFeatureFlags);
+                mFeatureFlags,
+                mSelectedUserInteractor);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceStateRepositoryTest.kt
new file mode 100644
index 0000000..4eb1591
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceStateRepositoryTest.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.unfold.updates
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.unfold.system.DeviceStateRepositoryImpl
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class DeviceStateRepositoryTest : SysuiTestCase() {
+
+    private val foldProvider = mock<FoldProvider>()
+    private val testScope = TestScope(UnconfinedTestDispatcher())
+
+    private val foldStateRepository = DeviceStateRepositoryImpl(foldProvider) { r -> r.run() }
+
+    @Test
+    fun onHingeAngleUpdate_received() =
+        testScope.runTest {
+            val flowValue = collectLastValue(foldStateRepository.isFolded)
+            val foldCallback = argumentCaptor<FoldProvider.FoldCallback>()
+
+            verify(foldProvider).registerCallback(capture(foldCallback), any())
+
+            foldCallback.value.onFoldUpdated(true)
+            assertThat(flowValue()).isEqualTo(true)
+
+            foldCallback.value.onFoldUpdated(false)
+            assertThat(flowValue()).isEqualTo(false)
+        }
+
+    @Test
+    fun onHingeAngleUpdate_unregisters() {
+        testScope.runTest {
+            val flowValue = collectLastValue(foldStateRepository.isFolded)
+
+            verify(foldProvider).registerCallback(any(), any())
+        }
+        verify(foldProvider).unregisterCallback(any())
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/FoldStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/FoldStateRepositoryTest.kt
new file mode 100644
index 0000000..0651323
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/FoldStateRepositoryTest.kt
@@ -0,0 +1,78 @@
+/*
+ * 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.unfold.updates
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.unfold.updates.FoldStateRepository.FoldUpdate
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class FoldStateRepositoryTest : SysuiTestCase() {
+
+    private val foldStateProvider = mock<FoldStateProvider>()
+    private val foldUpdatesListener = argumentCaptor<FoldStateProvider.FoldUpdatesListener>()
+    private val testScope = TestScope(UnconfinedTestDispatcher())
+
+    private val foldStateRepository = FoldStateRepositoryImpl(foldStateProvider)
+    @Test
+    fun onHingeAngleUpdate_received() =
+        testScope.runTest {
+            val flowValue = collectLastValue(foldStateRepository.hingeAngle)
+
+            verify(foldStateProvider).addCallback(capture(foldUpdatesListener))
+            foldUpdatesListener.value.onHingeAngleUpdate(42f)
+
+            assertThat(flowValue()).isEqualTo(42f)
+        }
+
+    @Test
+    fun onFoldUpdate_received() =
+        testScope.runTest {
+            val flowValue = collectLastValue(foldStateRepository.foldUpdate)
+
+            verify(foldStateProvider).addCallback(capture(foldUpdatesListener))
+            foldUpdatesListener.value.onFoldUpdate(FOLD_UPDATE_START_OPENING)
+
+            assertThat(flowValue()).isEqualTo(FoldUpdate.START_OPENING)
+        }
+
+    @Test
+    fun foldUpdates_mappedCorrectly() {
+        mapOf(
+                FOLD_UPDATE_START_OPENING to FoldUpdate.START_OPENING,
+                FOLD_UPDATE_START_CLOSING to FoldUpdate.START_CLOSING,
+                FOLD_UPDATE_FINISH_HALF_OPEN to FoldUpdate.FINISH_HALF_OPEN,
+                FOLD_UPDATE_FINISH_FULL_OPEN to FoldUpdate.FINISH_FULL_OPEN,
+                FOLD_UPDATE_FINISH_CLOSED to FoldUpdate.FINISH_CLOSED
+            )
+            .forEach { (id, expected) ->
+                assertThat(FoldUpdate.fromFoldUpdateId(id)).isEqualTo(expected)
+            }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt
new file mode 100644
index 0000000..60fe7d2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt
@@ -0,0 +1,50 @@
+package com.android.systemui.user.domain.interactor
+
+import android.content.pm.UserInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class SelectedUserInteractorTest : SysuiTestCase() {
+
+    private lateinit var underTest: SelectedUserInteractor
+
+    private val userRepository = FakeUserRepository()
+
+    @Before
+    fun setUp() {
+        userRepository.setUserInfos(USER_INFOS)
+        underTest =
+            SelectedUserInteractor(
+                userRepository,
+                FakeFeatureFlagsClassic().apply { set(Flags.REFACTOR_GETCURRENTUSER, true) }
+            )
+    }
+
+    @Test
+    fun getSelectedUserIdReturnsId() {
+        runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[0]) }
+
+        val actualId = underTest.getSelectedUserId()
+
+        assertThat(actualId).isEqualTo(USER_INFOS[0].id)
+    }
+
+    companion object {
+        private val USER_INFOS =
+            listOf(
+                UserInfo(100, "First user", 0),
+                UserInfo(101, "Second user", 0),
+            )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
index c56266d..1968d75 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
@@ -88,7 +88,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(JUnit4::class)
-class UserInteractorTest : SysuiTestCase() {
+class UserSwitcherInteractorTest : SysuiTestCase() {
 
     @Mock private lateinit var activityStarter: ActivityStarter
     @Mock private lateinit var manager: UserManager
@@ -102,7 +102,7 @@
     @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
 
-    private lateinit var underTest: UserInteractor
+    private lateinit var underTest: UserSwitcherInteractor
 
     private lateinit var spyContext: Context
     private lateinit var testScope: TestScope
@@ -665,8 +665,8 @@
             userRepository.setUserInfos(userInfos)
             userRepository.setSelectedUserInfo(userInfos[0])
             userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            val callback1: UserInteractor.UserCallback = mock()
-            val callback2: UserInteractor.UserCallback = mock()
+            val callback1: UserSwitcherInteractor.UserCallback = mock()
+            val callback2: UserSwitcherInteractor.UserCallback = mock()
             underTest.addCallback(callback1)
             underTest.addCallback(callback2)
             runCurrent()
@@ -1117,7 +1117,7 @@
             userRepository.setSelectedUserInfo(userInfo)
         }
         underTest =
-            UserInteractor(
+            UserSwitcherInteractor(
                 applicationContext = spyContext,
                 repository = userRepository,
                 activityStarter = activityStarter,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
index a8db368..7041eab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
@@ -42,7 +42,7 @@
 import com.android.systemui.user.domain.interactor.GuestUserInteractor
 import com.android.systemui.user.domain.interactor.HeadlessSystemUserMode
 import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -243,9 +243,8 @@
             userRepository.setSelectedUserInfo(USER_0)
         }
         return StatusBarUserChipViewModel(
-            context = context,
             interactor =
-                UserInteractor(
+                UserSwitcherInteractor(
                     applicationContext = context,
                     repository = userRepository,
                     activityStarter = activityStarter,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index c236b12..686f492 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -42,7 +42,7 @@
 import com.android.systemui.user.domain.interactor.GuestUserInteractor
 import com.android.systemui.user.domain.interactor.HeadlessSystemUserMode
 import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
 import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
 import com.android.systemui.user.shared.model.UserActionModel
 import com.android.systemui.util.mockito.any
@@ -157,8 +157,8 @@
 
         underTest =
             UserSwitcherViewModel(
-                userInteractor =
-                    UserInteractor(
+                userSwitcherInteractor =
+                    UserSwitcherInteractor(
                         applicationContext = context,
                         repository = userRepository,
                         activityStarter = activityStarter,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/EventsTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/EventsTest.java
index a853f1d..c69f5c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/EventsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/EventsTest.java
@@ -209,6 +209,11 @@
                         new int[]{MetricsEvent.POWER_OVERHEAT_ALARM,
                                 MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM},
                         Events.VolumeDialogEvent.USB_OVERHEAT_ALARM_DISMISSED},
+                {Events.EVENT_ODI_CAPTIONS_CLICK, null, "writeEvent odi_captions_click", null,
+                        Events.VolumeDialogEvent.VOLUME_DIALOG_ODI_CAPTIONS_CLICKED},
+                {Events.EVENT_ODI_CAPTIONS_TOOLTIP_CLICK, null,
+                        "writeEvent odi_captions_tooltip_click", null,
+                        Events.VolumeDialogEvent.VOLUME_DIALOG_ODI_CAPTIONS_TOOLTIP_CLICKED}
         });
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index b8f747b..c4c7472 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -53,11 +53,12 @@
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityManager;
 
+import androidx.test.core.view.MotionEventBuilder;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.jank.InteractionJankMonitor;
+import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.systemui.Prefs;
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.AnimatorTestRule;
 import com.android.systemui.dump.DumpManager;
@@ -66,6 +67,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.plugins.VolumeDialogController.State;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DevicePostureController;
@@ -76,6 +78,8 @@
 
 import dagger.Lazy;
 
+import junit.framework.Assert;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
@@ -699,6 +703,60 @@
         }
     }
 
+    /**
+     * The click should be a single tap, thus we inject a down and an up event.
+     */
+    @Test
+    public void clickCaptionsButton_logsUiEvent() {
+        UiEventLoggerFake logger = new UiEventLoggerFake();
+        Events.sUiEventLogger = logger;
+        MotionEvent down = MotionEventBuilder.newBuilder()
+                .setAction(MotionEvent.ACTION_DOWN).build();
+        MotionEvent up = MotionEventBuilder.newBuilder()
+                .setAction(MotionEvent.ACTION_UP).build();
+
+        mODICaptionsIcon.onTouchEvent(down);
+        mODICaptionsIcon.onTouchEvent(up);
+        mTestableLooper.moveTimeForward(300); // to confirm it was only a single tap
+        mTestableLooper.processAllMessages();
+
+        boolean foundCaptionLog = false;
+        for (UiEventLoggerFake.FakeUiEvent event : logger.getLogs()) {
+            if (event.eventId
+                    == Events.VolumeDialogEvent.VOLUME_DIALOG_ODI_CAPTIONS_CLICKED.getId()) {
+                foundCaptionLog = true;
+                break;
+            }
+        }
+        Assert.assertTrue("Did not log the captions button click.", foundCaptionLog);
+    }
+
+    /**
+     * Pressing the small x button at top right dismisses the captions tooltip.
+     */
+    @Test
+    public void dismissCaptionsTooltip_logsUiEvent() {
+        UiEventLoggerFake logger = new UiEventLoggerFake();
+        Events.sUiEventLogger = logger;
+        mDialog.showCaptionsTooltip();
+        assumeNotNull(mDialog.mODICaptionsTooltipView);
+        View dismissButton = mDialog.mODICaptionsTooltipView.findViewById(R.id.dismiss);
+
+        dismissButton.performClick();
+
+        boolean foundCaptionLog = false;
+        for (UiEventLoggerFake.FakeUiEvent event : logger.getLogs()) {
+            if (event.eventId
+                    == Events.VolumeDialogEvent.VOLUME_DIALOG_ODI_CAPTIONS_TOOLTIP_CLICKED.getId()
+            ) {
+                foundCaptionLog = true;
+                break;
+            }
+        }
+        Assert.assertTrue("Did not log the captions tooltip dismiss button click.",
+                foundCaptionLog);
+    }
+
     @After
     public void teardown() {
         // Detailed logs to track down timeout issues in b/299491332
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
index 468c5a7..fc2030f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
@@ -16,15 +16,20 @@
 
 package com.android.systemui.wallpapers;
 
+import static android.app.WallpaperManager.FLAG_LOCK;
+import static android.app.WallpaperManager.FLAG_SYSTEM;
+
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.hamcrest.Matchers.equalTo;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
@@ -111,7 +116,8 @@
         when(mWallpaperBitmap.getConfig()).thenReturn(Bitmap.Config.ARGB_8888);
 
         // set up wallpaper manager
-        when(mWallpaperManager.getBitmapAsUser(eq(ActivityManager.getCurrentUser()), anyBoolean()))
+        when(mWallpaperManager.getBitmapAsUser(
+                eq(ActivityManager.getCurrentUser()), anyBoolean(), anyInt(), anyBoolean()))
                 .thenReturn(mWallpaperBitmap);
         when(mMockContext.getSystemService(WallpaperManager.class)).thenReturn(mWallpaperManager);
 
@@ -208,6 +214,7 @@
         ImageWallpaper.CanvasEngine spyEngine = spy(engine);
         doNothing().when(spyEngine).drawFrameOnCanvas(any(Bitmap.class));
         doNothing().when(spyEngine).reportEngineShown(anyBoolean());
+        doReturn(FLAG_SYSTEM | FLAG_LOCK).when(spyEngine).getWallpaperFlags();
         doAnswer(invocation -> {
             ((ImageWallpaper.CanvasEngine) invocation.getMock()).onMiniBitmapUpdated();
             return null;
@@ -216,7 +223,7 @@
     }
 
     private void setBitmapDimensions(int bitmapWidth, int bitmapHeight) {
-        when(mWallpaperManager.peekBitmapDimensions())
+        when(mWallpaperManager.peekBitmapDimensions(anyInt(), anyBoolean()))
                 .thenReturn(new Rect(0, 0, bitmapWidth, bitmapHeight));
         when(mWallpaperBitmap.getWidth()).thenReturn(bitmapWidth);
         when(mWallpaperBitmap.getHeight()).thenReturn(bitmapHeight);
@@ -234,9 +241,7 @@
         clearInvocations(mSurfaceHolder);
         setBitmapDimensions(bitmapWidth, bitmapHeight);
 
-        ImageWallpaper imageWallpaper = createImageWallpaper();
-        ImageWallpaper.CanvasEngine engine =
-                (ImageWallpaper.CanvasEngine) imageWallpaper.onCreateEngine();
+        ImageWallpaper.CanvasEngine engine = getSpyEngine();
         engine.onCreate(mSurfaceHolder);
 
         verify(mSurfaceHolder, times(1)).setFixedSize(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index c832702..a42fa41 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -156,7 +156,8 @@
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
-import com.android.systemui.user.domain.interactor.UserInteractor;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.bubbles.Bubble;
@@ -324,6 +325,8 @@
     @Mock
     private ShadeWindowLogger mShadeWindowLogger;
     @Mock
+    private SelectedUserInteractor mSelectedUserInteractor;
+    @Mock
     private NotifPipelineFlags mNotifPipelineFlags;
     @Mock
     private Icon mAppBubbleIcon;
@@ -433,6 +436,7 @@
                 keyguardInteractor,
                 featureFlags,
                 mock(KeyguardSecurityModel.class),
+                mSelectedUserInteractor,
                 powerInteractor);
 
         ResourcesSplitShadeStateController splitShadeStateController =
@@ -450,7 +454,7 @@
                         keyguardTransitionInteractor,
                         powerInteractor,
                         new FakeUserSetupRepository(),
-                        mock(UserInteractor.class),
+                        mock(UserSwitcherInteractor.class),
                         new SharedNotificationContainerInteractor(
                                 configurationRepository,
                                 mContext,
@@ -476,7 +480,8 @@
                 mAuthController,
                 mShadeExpansionStateManager,
                 () -> mShadeInteractor,
-                mShadeWindowLogger
+                mShadeWindowLogger,
+                () -> mSelectedUserInteractor
         );
         mNotificationShadeWindowController.fetchWindowRootView();
         mNotificationShadeWindowController.attach();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index 30132f7..08adda3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -1,7 +1,8 @@
 package com.android.systemui.communal.data.repository
 
 import com.android.systemui.communal.data.model.CommunalWidgetMetadata
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 
@@ -11,7 +12,14 @@
     override val stopwatchAppWidgetInfo: Flow<CommunalAppWidgetInfo?> = _stopwatchAppWidgetInfo
     override var communalWidgetAllowlist: List<CommunalWidgetMetadata> = emptyList()
 
+    private val _communalWidgets = MutableStateFlow<List<CommunalWidgetContentModel>>(emptyList())
+    override val communalWidgets: Flow<List<CommunalWidgetContentModel>> = _communalWidgets
+
     fun setStopwatchAppWidgetInfo(appWidgetInfo: CommunalAppWidgetInfo) {
         _stopwatchAppWidgetInfo.value = appWidgetInfo
     }
+
+    fun setCommunalWidgets(inventory: List<CommunalWidgetContentModel>) {
+        _communalWidgets.value = inventory
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 9de7ad8..fae49b1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -29,12 +29,12 @@
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import dagger.Binds
 import dagger.Module
+import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import javax.inject.Inject
 
 /** Fake implementation of [KeyguardRepository] */
 @SysUISingleton
@@ -182,8 +182,8 @@
         _lastDozeTapToWakePosition.value = position
     }
 
-    fun setAodAvailable(isAodAvailable: Boolean) {
-        _isAodAvailable.value = isAodAvailable
+    override fun setAodAvailable(value: Boolean) {
+        _isAodAvailable.value = value
     }
 
     fun setDreaming(isDreaming: Boolean) {
@@ -202,8 +202,8 @@
         _dozeAmount.value = dozeAmount
     }
 
-    fun setBiometricUnlockState(state: BiometricUnlockModel) {
-        _biometricUnlockState.tryEmit(state)
+    override fun setBiometricUnlockState(value: BiometricUnlockModel) {
+        _biometricUnlockState.tryEmit(value)
     }
 
     fun setBiometricUnlockSource(source: BiometricUnlockSource?) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
index e160548..71e2bc1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -42,7 +42,7 @@
         _transitions.emit(step)
     }
 
-    override fun startTransition(info: TransitionInfo, resetIfCanceled: Boolean): UUID? {
+    override fun startTransition(info: TransitionInfo): UUID? {
         return if (info.animator == null) UUID.randomUUID() else null
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
index 8e96b52..fc34903 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
@@ -16,11 +16,8 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
-import android.app.ActivityManager
 import android.content.Context
 import android.os.Handler
-import android.os.UserManager
-import com.android.internal.logging.UiEventLogger
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
@@ -28,7 +25,6 @@
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.bouncer.ui.BouncerView
-import com.android.systemui.broadcast.FakeBroadcastDispatcher
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
@@ -36,21 +32,13 @@
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.power.data.repository.FakePowerRepository
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
 import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
-import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
 import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.user.domain.interactor.GuestUserInteractor
-import com.android.systemui.user.domain.interactor.HeadlessSystemUserMode
-import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.time.FakeSystemClock
-import com.android.systemui.utils.UserRestrictionChecker
-import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.test.TestScope
 import org.mockito.Mockito.mock
 
@@ -64,8 +52,6 @@
     fun create(
         context: Context,
         testScope: TestScope,
-        broadcastDispatcher: FakeBroadcastDispatcher,
-        dispatcher: CoroutineDispatcher,
         trustRepository: FakeTrustRepository = FakeTrustRepository(),
         keyguardRepository: FakeKeyguardRepository = FakeKeyguardRepository(),
         bouncerRepository: FakeKeyguardBouncerRepository = FakeKeyguardBouncerRepository(),
@@ -74,6 +60,7 @@
             FakeFeatureFlagsClassic().apply {
                 set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, true)
                 set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+                set(Flags.REFACTOR_GETCURRENTUSER, true)
             },
         powerRepository: FakePowerRepository = FakePowerRepository(),
         userRepository: FakeUserRepository = FakeUserRepository(),
@@ -92,6 +79,7 @@
                 keyguardUpdateMonitor,
                 trustRepository,
                 testScope.backgroundScope,
+                mock(SelectedUserInteractor::class.java),
             )
         val alternateBouncerInteractor =
             AlternateBouncerInteractor(
@@ -103,38 +91,11 @@
                 keyguardUpdateMonitor,
             )
         val powerInteractorWithDeps =
-                PowerInteractorFactory.create(
-                        repository = powerRepository,
-                )
-        val userInteractor =
-            UserInteractor(
-                applicationContext = context,
-                repository = userRepository,
-                mock(ActivityStarter::class.java),
-                keyguardInteractor =
-                    KeyguardInteractorFactory.create(
-                            repository = keyguardRepository,
-                            bouncerRepository = bouncerRepository,
-                            featureFlags = featureFlags,
-                        )
-                        .keyguardInteractor,
-                featureFlags = featureFlags,
-                manager = mock(UserManager::class.java),
-                headlessSystemUserMode = mock(HeadlessSystemUserMode::class.java),
-                applicationScope = testScope.backgroundScope,
-                telephonyInteractor =
-                    TelephonyInteractor(
-                        repository = FakeTelephonyRepository(),
-                    ),
-                broadcastDispatcher = broadcastDispatcher,
-                keyguardUpdateMonitor = keyguardUpdateMonitor,
-                backgroundDispatcher = dispatcher,
-                activityManager = mock(ActivityManager::class.java),
-                refreshUsersScheduler = mock(RefreshUsersScheduler::class.java),
-                guestUserInteractor = mock(GuestUserInteractor::class.java),
-                uiEventLogger = mock(UiEventLogger::class.java),
-                userRestrictionChecker = mock(UserRestrictionChecker::class.java),
+            PowerInteractorFactory.create(
+                repository = powerRepository,
             )
+        val selectedUserInteractor =
+            SelectedUserInteractor(repository = userRepository, flags = featureFlags)
         return WithDependencies(
             trustRepository = trustRepository,
             keyguardRepository = keyguardRepository,
@@ -149,7 +110,7 @@
                     primaryBouncerInteractor,
                     alternateBouncerInteractor,
                     powerInteractorWithDeps.powerInteractor,
-                    userInteractor,
+                    selectedUserInteractor,
                 ),
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
index 911eafa..cddb007 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
@@ -41,17 +41,17 @@
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.security.data.repository.SecurityRepository
 import com.android.systemui.security.data.repository.SecurityRepositoryImpl
-import com.android.systemui.settings.FakeUserTracker
-import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.statusbar.policy.FakeSecurityController
 import com.android.systemui.statusbar.policy.FakeUserInfoController
 import com.android.systemui.statusbar.policy.SecurityController
 import com.android.systemui.statusbar.policy.UserInfoController
 import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.user.data.repository.UserSwitcherRepository
 import com.android.systemui.user.data.repository.UserSwitcherRepositoryImpl
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.settings.FakeGlobalSettings
 import com.android.systemui.util.settings.GlobalSettings
@@ -102,7 +102,7 @@
         deviceProvisionedController: DeviceProvisionedController = mock(),
         qsSecurityFooterUtils: QSSecurityFooterUtils = mock(),
         fgsManagerController: FgsManagerController = mock(),
-        userInteractor: UserInteractor = mock(),
+        userSwitcherInteractor: UserSwitcherInteractor = mock(),
         securityRepository: SecurityRepository = securityRepository(),
         foregroundServicesRepository: ForegroundServicesRepository = foregroundServicesRepository(),
         userSwitcherRepository: UserSwitcherRepository = userSwitcherRepository(),
@@ -116,7 +116,7 @@
             deviceProvisionedController,
             qsSecurityFooterUtils,
             fgsManagerController,
-            userInteractor,
+            userSwitcherInteractor,
             securityRepository,
             foregroundServicesRepository,
             userSwitcherRepository,
@@ -149,7 +149,7 @@
         bgHandler: Handler = Handler(testableLooper.looper),
         bgDispatcher: CoroutineDispatcher = StandardTestDispatcher(scheduler),
         userManager: UserManager = mock(),
-        userTracker: UserTracker = FakeUserTracker(),
+        userRepository: UserRepository = FakeUserRepository(),
         userSwitcherController: UserSwitcherController = mock(),
         userInfoController: UserInfoController = FakeUserInfoController(),
         settings: GlobalSettings = FakeGlobalSettings(),
@@ -159,10 +159,10 @@
             bgHandler,
             bgDispatcher,
             userManager,
-            userTracker,
             userSwitcherController,
             userInfoController,
             settings,
+            userRepository,
         )
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
index 4307ff9..7494ccf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
@@ -69,10 +69,16 @@
         _userId = _userInfo.id
         _userHandle = UserHandle.of(_userId)
 
+        onBeforeUserSwitching()
         onUserChanging()
         onUserChanged()
     }
 
+    fun onBeforeUserSwitching(userId: Int = _userId) {
+        val copy = callbacks.toList()
+        copy.forEach { it.onBeforeUserSwitching(userId) }
+    }
+
     fun onUserChanging(userId: Int = _userId) {
         val copy = callbacks.toList()
         copy.forEach { it.onUserChanging(userId, userContext) {} }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java
index 197873f..288dcfe 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java
@@ -186,7 +186,7 @@
     }
 
     @Override
-    protected boolean initDataInjectionImpl(boolean enable) {
+    protected boolean initDataInjectionImpl(boolean enable, @DataInjectionMode int mode) {
         return false;
     }
 
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
index 5ffc094..7473ca6 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
@@ -22,6 +22,8 @@
 import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder
 import com.android.systemui.unfold.updates.DeviceFoldStateProvider
 import com.android.systemui.unfold.updates.FoldStateProvider
+import com.android.systemui.unfold.updates.FoldStateRepository
+import com.android.systemui.unfold.updates.FoldStateRepositoryImpl
 import com.android.systemui.unfold.updates.hinge.EmptyHingeAngleProvider
 import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
 import com.android.systemui.unfold.updates.hinge.HingeSensorAngleProvider
@@ -55,6 +57,12 @@
     fun unfoldKeyguardVisibilityManager(
         impl: UnfoldKeyguardVisibilityManagerImpl
     ): UnfoldKeyguardVisibilityManager = impl
+
+    @Provides
+    @Singleton
+    fun foldStateRepository(
+            impl: FoldStateRepositoryImpl
+    ): FoldStateRepository = impl
 }
 
 /**
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index 6743515..003013e 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -17,7 +17,6 @@
 
 import android.content.Context
 import android.os.Handler
-import android.os.Trace
 import android.util.Log
 import androidx.annotation.FloatRange
 import androidx.annotation.VisibleForTesting
@@ -130,7 +129,6 @@
                     "lastHingeAngleBeforeTransition: $lastHingeAngleBeforeTransition"
             )
         }
-        Trace.setCounter("DeviceFoldStateProvider#onHingeAngle", angle.toLong())
 
         val currentDirection =
                 if (angle < lastHingeAngle) FOLD_UPDATE_START_CLOSING else FOLD_UPDATE_START_OPENING
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldProvider.kt
index 6e87bee..ea6786e 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldProvider.kt
@@ -20,7 +20,7 @@
     fun registerCallback(callback: FoldCallback, executor: Executor)
     fun unregisterCallback(callback: FoldCallback)
 
-    interface FoldCallback {
+    fun interface FoldCallback {
         fun onFoldUpdated(isFolded: Boolean)
     }
 }
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateRepository.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateRepository.kt
new file mode 100644
index 0000000..61b0b40
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateRepository.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.unfold.updates
+
+import com.android.systemui.unfold.updates.FoldStateRepository.FoldUpdate
+import javax.inject.Inject
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.buffer
+import kotlinx.coroutines.flow.callbackFlow
+
+/**
+ * Allows to subscribe to main events related to fold/unfold process such as hinge angle update,
+ * start folding/unfolding, screen availability
+ */
+interface FoldStateRepository {
+    /** Latest fold update, as described by [FoldStateProvider.FoldUpdate]. */
+    val foldUpdate: Flow<FoldUpdate>
+
+    /** Provides the hinge angle while the fold/unfold is in progress. */
+    val hingeAngle: Flow<Float>
+
+    enum class FoldUpdate {
+        START_OPENING,
+        START_CLOSING,
+        FINISH_HALF_OPEN,
+        FINISH_FULL_OPEN,
+        FINISH_CLOSED;
+
+        companion object {
+            /** Maps the old [FoldStateProvider.FoldUpdate] to [FoldStateRepository.FoldUpdate]. */
+            fun fromFoldUpdateId(@FoldStateProvider.FoldUpdate oldId: Int): FoldUpdate {
+                return when (oldId) {
+                    FOLD_UPDATE_START_OPENING -> START_OPENING
+                    FOLD_UPDATE_START_CLOSING -> START_CLOSING
+                    FOLD_UPDATE_FINISH_HALF_OPEN -> FINISH_HALF_OPEN
+                    FOLD_UPDATE_FINISH_FULL_OPEN -> FINISH_FULL_OPEN
+                    FOLD_UPDATE_FINISH_CLOSED -> FINISH_CLOSED
+                    else -> error("FoldUpdateNotFound")
+                }
+            }
+        }
+    }
+}
+
+class FoldStateRepositoryImpl
+@Inject
+constructor(
+    private val foldStateProvider: FoldStateProvider,
+) : FoldStateRepository {
+
+    override val hingeAngle: Flow<Float>
+        get() =
+            callbackFlow {
+                    val callback =
+                        object : FoldStateProvider.FoldUpdatesListener {
+                            override fun onHingeAngleUpdate(angle: Float) {
+                                trySend(angle)
+                            }
+                        }
+                    foldStateProvider.addCallback(callback)
+                    awaitClose { foldStateProvider.removeCallback(callback) }
+                }
+                .buffer(capacity = Channel.CONFLATED)
+
+    override val foldUpdate: Flow<FoldUpdate>
+        get() =
+            callbackFlow {
+                    val callback =
+                        object : FoldStateProvider.FoldUpdatesListener {
+                            override fun onFoldUpdate(update: Int) {
+                                trySend(FoldUpdate.fromFoldUpdateId(update))
+                            }
+                        }
+                    foldStateProvider.addCallback(callback)
+                    awaitClose { foldStateProvider.removeCallback(callback) }
+                }
+                .buffer(capacity = Channel.CONFLATED)
+}
diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
index 3406102..98421a9 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -367,11 +367,7 @@
             ComponentName wpService = parseWallpaperComponent(infoStage, "wp");
             mSystemHasLiveComponent = wpService != null;
 
-            ComponentName kwpService = null;
-            boolean lockscreenLiveWallpaper = mWallpaperManager.isLockscreenLiveWallpaperEnabled();
-            if (lockscreenLiveWallpaper) {
-                kwpService = parseWallpaperComponent(infoStage, "kwp");
-            }
+            ComponentName kwpService = parseWallpaperComponent(infoStage, "kwp");
             mLockHasLiveComponent = kwpService != null;
             boolean separateLockWallpaper = mLockHasLiveComponent || lockImageStage.exists();
 
@@ -381,17 +377,16 @@
             // It is valid for the imagery to be absent; it means that we were not permitted
             // to back up the original image on the source device, or there was no user-supplied
             // wallpaper image present.
-            if (!lockscreenLiveWallpaper) restoreFromStage(imageStage, infoStage, "wp", sysWhich);
             if (lockImageStageExists) {
                 restoreFromStage(lockImageStage, infoStage, "kwp", FLAG_LOCK);
             }
-            if (lockscreenLiveWallpaper) restoreFromStage(imageStage, infoStage, "wp", sysWhich);
+            restoreFromStage(imageStage, infoStage, "wp", sysWhich);
 
             // And reset to the wallpaper service we should be using
-            if (lockscreenLiveWallpaper && mLockHasLiveComponent) {
-                updateWallpaperComponent(kwpService, false, FLAG_LOCK);
+            if (mLockHasLiveComponent) {
+                updateWallpaperComponent(kwpService, FLAG_LOCK);
             }
-            updateWallpaperComponent(wpService, !lockImageStageExists, sysWhich);
+            updateWallpaperComponent(wpService, sysWhich);
         } catch (Exception e) {
             Slog.e(TAG, "Unable to restore wallpaper: " + e.getMessage());
             mEventLogger.onRestoreException(e);
@@ -410,36 +405,24 @@
     }
 
     @VisibleForTesting
-    void updateWallpaperComponent(ComponentName wpService, boolean applyToLock, int which)
+    void updateWallpaperComponent(ComponentName wpService, int which)
             throws IOException {
-        boolean lockscreenLiveWallpaper = mWallpaperManager.isLockscreenLiveWallpaperEnabled();
         if (servicePackageExists(wpService)) {
             Slog.i(TAG, "Using wallpaper service " + wpService);
-            if (lockscreenLiveWallpaper) {
-                mWallpaperManager.setWallpaperComponentWithFlags(wpService, which);
-                if ((which & FLAG_LOCK) != 0) {
-                    mEventLogger.onLockLiveWallpaperRestored(wpService);
-                }
-                if ((which & FLAG_SYSTEM) != 0) {
-                    mEventLogger.onSystemLiveWallpaperRestored(wpService);
-                }
-                return;
-            }
-            mWallpaperManager.setWallpaperComponent(wpService);
-            if (applyToLock) {
-                // We have a live wallpaper and no static lock image,
-                // allow live wallpaper to show "through" on lock screen.
-                mWallpaperManager.clear(FLAG_LOCK);
+            mWallpaperManager.setWallpaperComponentWithFlags(wpService, which);
+            if ((which & FLAG_LOCK) != 0) {
                 mEventLogger.onLockLiveWallpaperRestored(wpService);
             }
-            mEventLogger.onSystemLiveWallpaperRestored(wpService);
+            if ((which & FLAG_SYSTEM) != 0) {
+                mEventLogger.onSystemLiveWallpaperRestored(wpService);
+            }
         } else {
             // If we've restored a live wallpaper, but the component doesn't exist,
             // we should log it as an error so we can easily identify the problem
             // in reports from users
             if (wpService != null) {
                 // TODO(b/268471749): Handle delayed case
-                applyComponentAtInstall(wpService, applyToLock, which);
+                applyComponentAtInstall(wpService, which);
                 Slog.w(TAG, "Wallpaper service " + wpService + " isn't available. "
                         + " Will try to apply later");
             }
@@ -579,21 +562,17 @@
         // Intentionally blank
     }
 
-    private void applyComponentAtInstall(ComponentName componentName, boolean applyToLock,
-            int which) {
+    private void applyComponentAtInstall(ComponentName componentName, int which) {
         PackageMonitor packageMonitor = getWallpaperPackageMonitor(
-                componentName, applyToLock, which);
+                componentName, which);
         packageMonitor.register(getBaseContext(), null, UserHandle.ALL, true);
     }
 
     @VisibleForTesting
-    PackageMonitor getWallpaperPackageMonitor(ComponentName componentName, boolean applyToLock,
-            int which) {
+    PackageMonitor getWallpaperPackageMonitor(ComponentName componentName, int which) {
         return new PackageMonitor() {
             @Override
             public void onPackageAdded(String packageName, int uid) {
-                boolean lockscreenLiveWallpaper =
-                        mWallpaperManager.isLockscreenLiveWallpaperEnabled();
                 if (!isDeviceInRestore()) {
                     // We don't want to reapply the wallpaper outside a restore.
                     unregister();
@@ -601,9 +580,11 @@
                     // We have finished restore and not succeeded, so let's log that as an error.
                     WallpaperEventLogger logger = new WallpaperEventLogger(
                             mBackupManager.getDelayedRestoreLogger());
-                    logger.onSystemLiveWallpaperRestoreFailed(
-                            WallpaperEventLogger.ERROR_LIVE_PACKAGE_NOT_INSTALLED);
-                    if (applyToLock) {
+                    if ((which & FLAG_SYSTEM) != 0) {
+                        logger.onSystemLiveWallpaperRestoreFailed(
+                                WallpaperEventLogger.ERROR_LIVE_PACKAGE_NOT_INSTALLED);
+                    }
+                    if ((which & FLAG_LOCK) != 0) {
                         logger.onLockLiveWallpaperRestoreFailed(
                                 WallpaperEventLogger.ERROR_LIVE_PACKAGE_NOT_INSTALLED);
                     }
@@ -614,37 +595,27 @@
 
                 if (componentName.getPackageName().equals(packageName)) {
                     Slog.d(TAG, "Applying component " + componentName);
-                    boolean success = lockscreenLiveWallpaper
-                            ? mWallpaperManager.setWallpaperComponentWithFlags(componentName, which)
-                            : mWallpaperManager.setWallpaperComponent(componentName);
+                    boolean success = mWallpaperManager.setWallpaperComponentWithFlags(
+                            componentName, which);
                     WallpaperEventLogger logger = new WallpaperEventLogger(
                             mBackupManager.getDelayedRestoreLogger());
                     if (success) {
-                        if (!lockscreenLiveWallpaper || (which & FLAG_SYSTEM) != 0) {
+                        if ((which & FLAG_SYSTEM) != 0) {
                             logger.onSystemLiveWallpaperRestored(componentName);
                         }
-                        if (lockscreenLiveWallpaper && (which & FLAG_LOCK) != 0) {
+                        if ((which & FLAG_LOCK) != 0) {
                             logger.onLockLiveWallpaperRestored(componentName);
                         }
                     } else {
-                        if (!lockscreenLiveWallpaper || (which & FLAG_SYSTEM) != 0) {
+                        if ((which & FLAG_SYSTEM) != 0) {
                             logger.onSystemLiveWallpaperRestoreFailed(
                                     WallpaperEventLogger.ERROR_SET_COMPONENT_EXCEPTION);
                         }
-                        if (lockscreenLiveWallpaper && (which & FLAG_LOCK) != 0) {
+                        if ((which & FLAG_LOCK) != 0) {
                             logger.onLockLiveWallpaperRestoreFailed(
                                     WallpaperEventLogger.ERROR_SET_COMPONENT_EXCEPTION);
                         }
                     }
-                    if (applyToLock && !lockscreenLiveWallpaper) {
-                        try {
-                            mWallpaperManager.clear(FLAG_LOCK);
-                            logger.onLockLiveWallpaperRestored(componentName);
-                        } catch (IOException e) {
-                            Slog.w(TAG, "Failed to apply live wallpaper to lock screen: " + e);
-                            logger.onLockLiveWallpaperRestoreFailed(e.getClass().getName());
-                        }
-                    }
                     // We're only expecting to restore the wallpaper component once.
                     unregister();
                     mBackupManager.reportDelayedRestoreResult(logger.getBackupRestoreLogger());
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
index dc1126e..4c224fb 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
@@ -116,8 +116,6 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-
-        when(mWallpaperManager.isLockscreenLiveWallpaperEnabled()).thenReturn(true);
         when(mWallpaperManager.isWallpaperBackupEligible(eq(FLAG_SYSTEM))).thenReturn(true);
         when(mWallpaperManager.isWallpaperBackupEligible(eq(FLAG_LOCK))).thenReturn(true);
 
@@ -363,25 +361,19 @@
     @Test
     public void testUpdateWallpaperComponent_doesApplyLater() throws IOException {
         mWallpaperBackupAgent.mIsDeviceInRestore = true;
-
         mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
-                /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM);
+                /* which */ FLAG_LOCK | FLAG_SYSTEM);
 
         // Imitate wallpaper component installation.
         mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
                 /* uid */0);
-        if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
-            verify(mWallpaperManager, times(1))
-                    .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK | FLAG_SYSTEM);
-            verify(mWallpaperManager, never())
-                    .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_SYSTEM);
-            verify(mWallpaperManager, never())
-                    .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK);
-            verify(mWallpaperManager, never()).clear(anyInt());
-        } else {
-            verify(mWallpaperManager, times(1)).setWallpaperComponent(mWallpaperComponent);
-            verify(mWallpaperManager, times(1)).clear(eq(FLAG_LOCK));
-        }
+        verify(mWallpaperManager, times(1))
+                .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK | FLAG_SYSTEM);
+        verify(mWallpaperManager, never())
+                .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_SYSTEM);
+        verify(mWallpaperManager, never())
+                .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK);
+        verify(mWallpaperManager, never()).clear(anyInt());
     }
 
     @Test
@@ -390,24 +382,19 @@
         mWallpaperBackupAgent.mIsDeviceInRestore = true;
 
         mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
-                /* applyToLock */ false, FLAG_SYSTEM);
+                /* which */ FLAG_SYSTEM);
 
         // Imitate wallpaper component installation.
         mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
                 /* uid */0);
 
-        if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
-            verify(mWallpaperManager, times(1))
-                    .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_SYSTEM);
-            verify(mWallpaperManager, never())
-                    .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK);
-            verify(mWallpaperManager, never())
-                    .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK | FLAG_SYSTEM);
-            verify(mWallpaperManager, never()).clear(anyInt());
-        } else {
-            verify(mWallpaperManager, times(1)).setWallpaperComponent(mWallpaperComponent);
-            verify(mWallpaperManager, never()).clear(eq(FLAG_LOCK));
-        }
+        verify(mWallpaperManager, times(1))
+                .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_SYSTEM);
+        verify(mWallpaperManager, never())
+                .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK);
+        verify(mWallpaperManager, never())
+                .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK | FLAG_SYSTEM);
+        verify(mWallpaperManager, never()).clear(anyInt());
     }
 
     @Test
@@ -416,7 +403,7 @@
         mWallpaperBackupAgent.mIsDeviceInRestore = false;
 
         mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
-                /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM);
+                /* which */ FLAG_LOCK | FLAG_SYSTEM);
 
         // Imitate wallpaper component installation.
         mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
@@ -432,7 +419,7 @@
         mWallpaperBackupAgent.mIsDeviceInRestore = false;
 
         mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
-                /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM);
+                /* which */ FLAG_LOCK | FLAG_SYSTEM);
 
         // Imitate "wrong" wallpaper component installation.
         mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(/* packageName */"",
@@ -622,7 +609,7 @@
     }
 
     @Test
-    public void testOnRestore_systemWallpaperImgSuccess_logsSuccess() throws Exception {
+    public void testOnRestore_wallpaperImgSuccess_logsSuccess() throws Exception {
         mockStagedWallpaperFile(WALLPAPER_INFO_STAGE);
         mockStagedWallpaperFile(SYSTEM_WALLPAPER_STAGE);
         mWallpaperBackupAgent.onCreate(USER_HANDLE, BackupAnnotations.BackupDestination.CLOUD,
@@ -630,17 +617,16 @@
 
         mWallpaperBackupAgent.onRestoreFinished();
 
+        // wallpaper will be applied to home & lock screen, a success for both screens in expected
         DataTypeResult result = getLoggingResult(WALLPAPER_IMG_SYSTEM,
                 mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
         assertThat(result).isNotNull();
         assertThat(result.getSuccessCount()).isEqualTo(1);
 
-        if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
-            result = getLoggingResult(WALLPAPER_IMG_LOCK,
-                    mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
-            assertThat(result).isNotNull();
-            assertThat(result.getSuccessCount()).isEqualTo(1);
-        }
+        result = getLoggingResult(WALLPAPER_IMG_LOCK,
+                mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
+        assertThat(result).isNotNull();
+        assertThat(result.getSuccessCount()).isEqualTo(1);
     }
 
     @Test
@@ -758,7 +744,7 @@
         mWallpaperBackupAgent.setBackupManagerForTesting(mBackupManager);
 
         mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
-                /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM);
+                /* which */ FLAG_LOCK | FLAG_SYSTEM);
         // Imitate wallpaper component installation.
         mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
                 /* uid */0);
@@ -782,7 +768,7 @@
         mWallpaperBackupAgent.setBackupManagerForTesting(mBackupManager);
 
         mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
-                /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM);
+                /* which */ FLAG_LOCK | FLAG_SYSTEM);
         // Imitate wallpaper component installation.
         mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
                 /* uid */0);
@@ -804,7 +790,7 @@
         mWallpaperBackupAgent.setBackupManagerForTesting(mBackupManager);
 
         mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
-                /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM);
+                /* which */ FLAG_LOCK | FLAG_SYSTEM);
 
         // Imitate wallpaper component installation.
         mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
@@ -938,10 +924,8 @@
         }
 
         @Override
-        PackageMonitor getWallpaperPackageMonitor(ComponentName componentName,
-                boolean applyToLock, int which) {
-            mWallpaperPackageMonitor = super.getWallpaperPackageMonitor(
-                    componentName, applyToLock, which);
+        PackageMonitor getWallpaperPackageMonitor(ComponentName componentName, int which) {
+            mWallpaperPackageMonitor = super.getWallpaperPackageMonitor(componentName, which);
             return mWallpaperPackageMonitor;
         }
 
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index f09cb19..75ecdb7 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -1,32 +1,6 @@
 package: "com.android.server.accessibility"
 
-flag {
-    name: "proxy_use_apps_on_virtual_device_listener"
-    namespace: "accessibility"
-    description: "Fixes race condition described in b/286587811"
-    bug: "286587811"
-}
-
-flag {
-    name: "enable_magnification_multiple_finger_multiple_tap_gesture"
-    namespace: "accessibility"
-    description: "Whether to enable multi-finger-multi-tap gesture for magnification"
-    bug: "257274411"
-}
-
-flag {
-    name: "enable_magnification_joystick"
-    namespace: "accessibility"
-    description: "Whether to enable joystick controls for magnification"
-    bug: "297211257"
-}
-
-flag {
-    name: "send_a11y_events_based_on_state"
-    namespace: "accessibility"
-    description: "Sends accessibility events in TouchExplorer#onAccessibilityEvent based on internal state to keep it consistent. This reduces test flakiness."
-    bug: "295575684"
-}
+# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
 
 flag {
     name: "add_window_token_without_lock"
@@ -36,13 +10,6 @@
 }
 
 flag {
-    name: "pinch_zoom_zero_min_span"
-    namespace: "accessibility"
-    description: "Whether to set min span of ScaleGestureDetector to zero."
-    bug: "295327792"
-}
-
-flag {
     name: "deprecate_package_list_observer"
     namespace: "accessibility"
     description: "Stops using the deprecated PackageListObserver."
@@ -50,10 +17,38 @@
 }
 
 flag {
-    name: "scan_packages_without_lock"
+    name: "disable_continuous_shortcut_on_force_stop"
     namespace: "accessibility"
-    description: "Scans packages for accessibility service/activity info without holding the A11yMS lock"
-    bug: "295969873"
+    description: "When a package is force stopped, remove the button shortcuts of any continuously-running shortcuts."
+    bug: "198018180"
+}
+
+flag {
+    name: "enable_magnification_joystick"
+    namespace: "accessibility"
+    description: "Whether to enable joystick controls for magnification"
+    bug: "297211257"
+}
+
+flag {
+    name: "enable_magnification_multiple_finger_multiple_tap_gesture"
+    namespace: "accessibility"
+    description: "Whether to enable multi-finger-multi-tap gesture for magnification"
+    bug: "257274411"
+}
+
+flag {
+    name: "pinch_zoom_zero_min_span"
+    namespace: "accessibility"
+    description: "Whether to set min span of ScaleGestureDetector to zero."
+    bug: "295327792"
+}
+
+flag {
+    name: "proxy_use_apps_on_virtual_device_listener"
+    namespace: "accessibility"
+    description: "Fixes race condition described in b/286587811"
+    bug: "286587811"
 }
 
 flag {
@@ -62,3 +57,17 @@
     description: "Reduces touch exploration sensitivity by only sending a hover event when the ifnger has moved the amount of pixels defined by the system's touch slop."
     bug: "303677860"
 }
+
+flag {
+    name: "scan_packages_without_lock"
+    namespace: "accessibility"
+    description: "Scans packages for accessibility service/activity info without holding the A11yMS lock"
+    bug: "295969873"
+}
+
+flag {
+    name: "send_a11y_events_based_on_state"
+    namespace: "accessibility"
+    description: "Sends accessibility events in TouchExplorer#onAccessibilityEvent based on internal state to keep it consistent. This reduces test flakiness."
+    bug: "295575684"
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index cd83f8f..5af80da 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -68,7 +68,7 @@
      *
      * @see #setUserAndEnabledFeatures(int, int)
      */
-    static final int FLAG_FEATURE_SCREEN_MAGNIFIER = 0x00000001;
+    static final int FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP = 0x00000001;
 
     /**
      * Flag for enabling the touch exploration feature.
@@ -100,7 +100,7 @@
 
     /**
      * Flag for enabling the feature to control the screen magnifier. If
-     * {@link #FLAG_FEATURE_SCREEN_MAGNIFIER} is set this flag is ignored
+     * {@link #FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP} is set this flag is ignored
      * as the screen magnifier feature performs a super set of the work
      * performed by this feature.
      *
@@ -149,7 +149,7 @@
             FLAG_FEATURE_INJECT_MOTION_EVENTS
                     | FLAG_FEATURE_AUTOCLICK
                     | FLAG_FEATURE_TOUCH_EXPLORATION
-                    | FLAG_FEATURE_SCREEN_MAGNIFIER
+                    | FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP
                     | FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER
                     | FLAG_SERVICE_HANDLES_DOUBLE_TAP
                     | FLAG_REQUEST_MULTI_FINGER_GESTURES
@@ -530,7 +530,7 @@
         }
 
         if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0
-                || ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0)
+                || ((mEnabledFeatures & FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP) != 0)
                 || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) {
             final MagnificationGestureHandler magnificationGestureHandler =
                     createMagnificationGestureHandler(displayId,
@@ -648,7 +648,7 @@
     private MagnificationGestureHandler createMagnificationGestureHandler(
             int displayId, Context displayContext) {
         final boolean detectControlGestures = (mEnabledFeatures
-                & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0;
+                & FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP) != 0;
         final boolean triggerable = (mEnabledFeatures
                 & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0;
         MagnificationGestureHandler magnificationGestureHandler;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 8c1d444..87f9cf1 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.accessibility;
 
+import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON;
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_MANAGER;
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_MANAGER_CLIENT;
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT;
@@ -182,6 +183,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.Executors;
 import java.util.function.Consumer;
@@ -650,6 +652,16 @@
         }
     }
 
+    /**
+     * Returns the lock object for any synchronized test blocks.
+     * Should not be used outside of testing.
+     * @return lock object.
+     */
+    @VisibleForTesting
+    Object getLock() {
+        return mLock;
+    }
+
     AccessibilityUserState getCurrentUserState() {
         synchronized (mLock) {
             return getCurrentUserStateLocked();
@@ -746,6 +758,62 @@
         }
     }
 
+    /**
+     * Handles a package or packages being force stopped.
+     * Will disable any relevant services,
+     * and remove any button targets of continuous services,
+     * denoted by {@link AccessibilityServiceInfo#FLAG_REQUEST_ACCESSIBILITY_BUTTON}.
+     * If the result is {@code true},
+     * then {@link AccessibilityManagerService#onUserStateChangedLocked(
+     * AccessibilityUserState, boolean)} should be called afterwards.
+     *
+     * @param packages list of packages that have stopped.
+     * @param userState user state to be read & modified.
+     * @return {@code true} if a service was enabled or a button target was removed,
+     * {@code false} otherwise.
+     */
+    @VisibleForTesting
+    boolean onPackagesForceStoppedLocked(
+            String[] packages, AccessibilityUserState userState) {
+        final List<String> continuousServicePackages =
+                userState.mInstalledServices.stream().filter(service ->
+                        (service.flags & FLAG_REQUEST_ACCESSIBILITY_BUTTON)
+                                == FLAG_REQUEST_ACCESSIBILITY_BUTTON
+                ).map(service -> service.getComponentName().flattenToString()).toList();
+
+        boolean enabledServicesChanged = false;
+        final Iterator<ComponentName> it = userState.mEnabledServices.iterator();
+        while (it.hasNext()) {
+            final ComponentName comp = it.next();
+            final String compPkg = comp.getPackageName();
+            for (String pkg : packages) {
+                if (compPkg.equals(pkg)) {
+                    it.remove();
+                    userState.getBindingServicesLocked().remove(comp);
+                    userState.getCrashedServicesLocked().remove(comp);
+                    enabledServicesChanged = true;
+                }
+            }
+        }
+        if (enabledServicesChanged) {
+            persistComponentNamesToSettingLocked(
+                    Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                    userState.mEnabledServices, userState.mUserId);
+        }
+
+        boolean buttonTargetsChanged = userState.mAccessibilityButtonTargets.removeIf(
+                target -> continuousServicePackages.stream().anyMatch(
+                        pkg -> Objects.equals(target, pkg)));
+        if (buttonTargetsChanged) {
+            persistColonDelimitedSetToSettingLocked(
+                    Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
+                    userState.mUserId,
+                    userState.mAccessibilityButtonTargets, str -> str);
+        }
+
+        return enabledServicesChanged || buttonTargetsChanged;
+    }
+
     @VisibleForTesting
     PackageMonitor getPackageMonitor() {
         return mPackageMonitor;
@@ -850,6 +918,16 @@
                 }
             }
 
+            /**
+             * Handles instances in which a package or packages have forcibly stopped.
+             *
+             * @param intent intent containing package event information.
+             * @param uid linux process user id (different from Android user id).
+             * @param packages array of package names that have stopped.
+             * @param doit whether to try and handle the stop or just log the trace.
+             *
+             * @return {@code true} if package should be restarted, {@code false} otherwise.
+             */
             @Override
             public boolean onHandleForceStop(Intent intent, String[] packages,
                     int uid, boolean doit) {
@@ -867,26 +945,36 @@
                         return false;
                     }
                     final AccessibilityUserState userState = getUserStateLocked(userId);
-                    final Iterator<ComponentName> it = userState.mEnabledServices.iterator();
-                    while (it.hasNext()) {
-                        final ComponentName comp = it.next();
-                        final String compPkg = comp.getPackageName();
-                        for (String pkg : packages) {
-                            if (compPkg.equals(pkg)) {
-                                if (!doit) {
-                                    return true;
+
+                    if (Flags.disableContinuousShortcutOnForceStop()) {
+                        if (doit && onPackagesForceStoppedLocked(packages, userState)) {
+                            onUserStateChangedLocked(userState);
+                            return false;
+                        } else {
+                            return true;
+                        }
+                    } else {
+                        final Iterator<ComponentName> it = userState.mEnabledServices.iterator();
+                        while (it.hasNext()) {
+                            final ComponentName comp = it.next();
+                            final String compPkg = comp.getPackageName();
+                            for (String pkg : packages) {
+                                if (compPkg.equals(pkg)) {
+                                    if (!doit) {
+                                        return true;
+                                    }
+                                    it.remove();
+                                    userState.getBindingServicesLocked().remove(comp);
+                                    userState.getCrashedServicesLocked().remove(comp);
+                                    persistComponentNamesToSettingLocked(
+                                            Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                                            userState.mEnabledServices, userId);
+                                    onUserStateChangedLocked(userState);
                                 }
-                                it.remove();
-                                userState.getBindingServicesLocked().remove(comp);
-                                userState.getCrashedServicesLocked().remove(comp);
-                                persistComponentNamesToSettingLocked(
-                                        Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
-                                        userState.mEnabledServices, userId);
-                                onUserStateChangedLocked(userState);
                             }
                         }
+                        return false;
                     }
-                    return false;
                 }
             }
         };
@@ -2452,7 +2540,8 @@
      * @param userId The user id.
      * @param outComponentNames The output component names.
      */
-    private void readComponentNamesFromSettingLocked(String settingName, int userId,
+    @VisibleForTesting
+    void readComponentNamesFromSettingLocked(String settingName, int userId,
             Set<ComponentName> outComponentNames) {
         readColonDelimitedSettingToSet(settingName, userId,
                 str -> ComponentName.unflattenFromString(str), outComponentNames);
@@ -2481,7 +2570,19 @@
                 componentName -> componentName.flattenToShortString());
     }
 
-    private <T> void readColonDelimitedSettingToSet(String settingName, int userId,
+    /**
+     * Reads a colon delimited setting,
+     * passes the values through a function,
+     * then stores the values in a provided set.
+     *
+     * @param settingName Name of setting.
+     * @param userId user id corresponding to setting.
+     * @param toItem function mapping values to the output set.
+     * @param outSet output set to write to.
+     * @param <T> type of output set.
+     */
+    @VisibleForTesting
+    <T> void readColonDelimitedSettingToSet(String settingName, int userId,
             Function<String, T> toItem, Set<T> outSet) {
         final String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(),
                 settingName, userId);
@@ -2693,8 +2794,9 @@
         AccessibilityInputFilter inputFilter = null;
         synchronized (mLock) {
             int flags = 0;
-            if (userState.isDisplayMagnificationEnabledLocked()) {
-                flags |= AccessibilityInputFilter.FLAG_FEATURE_SCREEN_MAGNIFIER;
+            if (userState.isMagnificationSingleFingerTripleTapEnabledLocked()) {
+                flags |= AccessibilityInputFilter
+                        .FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP;
             }
             if (userState.isShortcutMagnificationEnabledLocked()) {
                 flags |= AccessibilityInputFilter.FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER;
@@ -3047,12 +3149,14 @@
     }
 
     private boolean readMagnificationEnabledSettingsLocked(AccessibilityUserState userState) {
-        final boolean displayMagnificationEnabled = Settings.Secure.getIntForUser(
+        final boolean magnificationSingleFingerTripleTapEnabled = Settings.Secure.getIntForUser(
                 mContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
                 0, userState.mUserId) == 1;
-        if ((displayMagnificationEnabled != userState.isDisplayMagnificationEnabledLocked())) {
-            userState.setDisplayMagnificationEnabledLocked(displayMagnificationEnabled);
+        if ((magnificationSingleFingerTripleTapEnabled
+                != userState.isMagnificationSingleFingerTripleTapEnabledLocked())) {
+            userState.setMagnificationSingleFingerTripleTapEnabledLocked(
+                    magnificationSingleFingerTripleTapEnabled);
             return true;
         }
         return false;
@@ -3292,7 +3396,7 @@
         // We would skip overlay display because it uses overlay window to simulate secondary
         // displays in one display. It's not a real display and there's no input events for it.
         final ArrayList<Display> displays = getValidDisplayList();
-        if (userState.isDisplayMagnificationEnabledLocked()
+        if (userState.isMagnificationSingleFingerTripleTapEnabledLocked()
                 || userState.isShortcutMagnificationEnabledLocked()) {
             for (int i = 0; i < displays.size(); i++) {
                 final Display display = displays.get(i);
@@ -3321,7 +3425,7 @@
             return;
         }
         final boolean connect = (userState.isShortcutMagnificationEnabledLocked()
-                || userState.isDisplayMagnificationEnabledLocked())
+                || userState.isMagnificationSingleFingerTripleTapEnabledLocked())
                 && (userState.getMagnificationCapabilitiesLocked()
                 != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN)
                 || userHasMagnificationServicesLocked(userState);
@@ -3472,7 +3576,7 @@
                 return true;
             }
             final boolean requestA11yButton = (serviceInfo.flags
-                    & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
+                    & FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
             if (requestA11yButton && !userState.mEnabledServices.contains(componentName)) {
                 // An a11y service targeting sdk version > Q and request A11y button and is assigned
                 // to a11y btn should be in the enabled list.
@@ -3773,7 +3877,7 @@
             final int targetSdk = installedServiceInfo.getResolveInfo()
                     .serviceInfo.applicationInfo.targetSdkVersion;
             final boolean requestA11yButton = (installedServiceInfo.flags
-                    & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
+                    & FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
             // Turns on / off the accessibility service
             if ((targetSdk <= Build.VERSION_CODES.Q && shortcutType == ACCESSIBILITY_SHORTCUT_KEY)
                     || (targetSdk > Build.VERSION_CODES.Q && !requestA11yButton)) {
@@ -4825,8 +4929,8 @@
         private final Uri mTouchExplorationEnabledUri = Settings.Secure.getUriFor(
                 Settings.Secure.TOUCH_EXPLORATION_ENABLED);
 
-        private final Uri mDisplayMagnificationEnabledUri = Settings.Secure.getUriFor(
-                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED);
+        private final Uri mMagnificationmSingleFingerTripleTapEnabledUri = Settings.Secure
+                .getUriFor(Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED);
 
         private final Uri mAutoclickEnabledUri = Settings.Secure.getUriFor(
                 Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED);
@@ -4883,7 +4987,7 @@
         public void register(ContentResolver contentResolver) {
             contentResolver.registerContentObserver(mTouchExplorationEnabledUri,
                     false, this, UserHandle.USER_ALL);
-            contentResolver.registerContentObserver(mDisplayMagnificationEnabledUri,
+            contentResolver.registerContentObserver(mMagnificationmSingleFingerTripleTapEnabledUri,
                     false, this, UserHandle.USER_ALL);
             contentResolver.registerContentObserver(mAutoclickEnabledUri,
                     false, this, UserHandle.USER_ALL);
@@ -4931,7 +5035,7 @@
                     if (readTouchExplorationEnabledSettingLocked(userState)) {
                         onUserStateChangedLocked(userState);
                     }
-                } else if (mDisplayMagnificationEnabledUri.equals(uri)) {
+                } else if (mMagnificationmSingleFingerTripleTapEnabledUri.equals(uri)) {
                     if (readMagnificationEnabledSettingsLocked(userState)) {
                         onUserStateChangedLocked(userState);
                     }
@@ -5006,7 +5110,7 @@
         updateWindowMagnificationConnectionIfNeeded(userState);
         // Remove magnification button UI when the magnification capability is not all mode or
         // magnification is disabled.
-        if (!(userState.isDisplayMagnificationEnabledLocked()
+        if (!(userState.isMagnificationSingleFingerTripleTapEnabledLocked()
                 || userState.isShortcutMagnificationEnabledLocked())
                 || userState.getMagnificationCapabilitiesLocked()
                 != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 693526a..b4efec1 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -109,7 +109,7 @@
     private boolean mBindInstantServiceAllowed;
     private boolean mIsAudioDescriptionByDefaultRequested;
     private boolean mIsAutoclickEnabled;
-    private boolean mIsDisplayMagnificationEnabled;
+    private boolean mIsMagnificationSingleFingerTripleTapEnabled;
     private boolean mIsFilterKeyEventsEnabled;
     private boolean mIsPerformGesturesEnabled;
     private boolean mAccessibilityFocusOnlyInActiveWindow;
@@ -211,7 +211,7 @@
         mRequestMultiFingerGestures = false;
         mRequestTwoFingerPassthrough = false;
         mSendMotionEventsEnabled = false;
-        mIsDisplayMagnificationEnabled = false;
+        mIsMagnificationSingleFingerTripleTapEnabled = false;
         mIsAutoclickEnabled = false;
         mUserNonInteractiveUiTimeout = 0;
         mUserInteractiveUiTimeout = 0;
@@ -520,7 +520,7 @@
                 .append(String.valueOf(mRequestTwoFingerPassthrough));
         pw.append(", sendMotionEventsEnabled").append(String.valueOf(mSendMotionEventsEnabled));
         pw.append(", displayMagnificationEnabled=").append(String.valueOf(
-                mIsDisplayMagnificationEnabled));
+                mIsMagnificationSingleFingerTripleTapEnabled));
         pw.append(", autoclickEnabled=").append(String.valueOf(mIsAutoclickEnabled));
         pw.append(", nonInteractiveUiTimeout=").append(String.valueOf(mNonInteractiveUiTimeout));
         pw.append(", interactiveUiTimeout=").append(String.valueOf(mInteractiveUiTimeout));
@@ -625,12 +625,12 @@
         mIsAutoclickEnabled = enabled;
     }
 
-    public boolean isDisplayMagnificationEnabledLocked() {
-        return mIsDisplayMagnificationEnabled;
+    public boolean isMagnificationSingleFingerTripleTapEnabledLocked() {
+        return mIsMagnificationSingleFingerTripleTapEnabled;
     }
 
-    public void setDisplayMagnificationEnabledLocked(boolean enabled) {
-        mIsDisplayMagnificationEnabled = enabled;
+    public void setMagnificationSingleFingerTripleTapEnabledLocked(boolean enabled) {
+        mIsMagnificationSingleFingerTripleTapEnabled = enabled;
     }
 
     public boolean isFilterKeyEventsEnabledLocked() {
diff --git a/services/core/Android.bp b/services/core/Android.bp
index e5225f6..b696998 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -96,11 +96,26 @@
     out: ["com/android/server/location/contexthub/ContextHubStatsLog.java"],
 }
 
+/*
+ * This module is used to refer aconfig flag libraries that are
+ * added to the framework via static_libs.
+ * These libraries are referred here via libs prevent duplication of classes in both
+ * the framework and the system server.
+*/
+java_defaults {
+    name: "shared-framework-aconfig-libs",
+    libs: [
+        "display_flags_lib",
+        "camera_platform_flags_core_java_lib",
+    ],
+}
+
 java_library_static {
     name: "services.core.unboosted",
     defaults: [
         "platform_service_defaults",
         "android.hardware.power-java_static",
+        "shared-framework-aconfig-libs",
     ],
     srcs: [
         ":android.hardware.biometrics.face-V3-java-source",
@@ -121,6 +136,7 @@
         ":display-device-config",
         ":display-layout-config",
         ":device-state-config",
+        ":framework-core-nfc-infcadapter-sources",
         "java/com/android/server/EventLogTags.logtags",
         "java/com/android/server/am/EventLogTags.logtags",
         "java/com/android/server/wm/EventLogTags.logtags",
@@ -180,8 +196,8 @@
         "android.hardware.rebootescrow-V1-java",
         "android.hardware.power.stats-V2-java",
         "android.hidl.manager-V1.2-java",
+        "android.nfc.flags-aconfig-java",
         "cbor-java",
-        "display_flags_lib",
         "icu4j_calendar_astronomer",
         "android.security.aaid_aidl-java",
         "netd-client",
@@ -191,9 +207,8 @@
         "ImmutabilityAnnotation",
         "securebox",
         "apache-commons-math",
-        "power_optimization_flags_lib",
+        "backstage_power_flags_lib",
         "notification_flags_lib",
-        "camera_platform_flags_core_java_lib",
         "biometrics_flags_lib",
         "am_flags_lib",
     ],
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 8df5456..638abdb 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -1387,11 +1387,12 @@
             @UserIdInt int userId);
 
     /**
-     * Tells PackageManager when a component (except BroadcastReceivers) of the package is used
+     * Tells PackageManager when a component of the package is used
      * and the package should get out of stopped state and be enabled.
      */
     public abstract void notifyComponentUsed(@NonNull String packageName,
-            @UserIdInt int userId, @NonNull String recentCallingPackage, @NonNull String debugInfo);
+            @UserIdInt int userId, @Nullable String recentCallingPackage,
+            @NonNull String debugInfo);
 
     /** @deprecated For legacy shell command only. */
     @Deprecated
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index beea063..8624dd5 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1236,7 +1236,7 @@
     private void onUserStopped(int userId) {
         Slog.d(TAG, "onUserStopped " + userId);
 
-        Watchdog.getInstance().setOneOffTimeoutForMonitors(
+        Watchdog.getInstance().pauseWatchingMonitorsFor(
                 SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#onUserStopped might be slow");
         try {
             mVold.onUserStopped(userId);
@@ -1320,7 +1320,7 @@
                 unlockedUsers.add(userId);
             }
         }
-        Watchdog.getInstance().setOneOffTimeoutForMonitors(
+        Watchdog.getInstance().pauseWatchingMonitorsFor(
                 SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#onUserStopped might be slow");
         for (Integer userId : unlockedUsers) {
             try {
@@ -2341,7 +2341,7 @@
         try {
             // TODO(b/135341433): Remove cautious logging when FUSE is stable
             Slog.i(TAG, "Mounting volume " + vol);
-            Watchdog.getInstance().setOneOffTimeoutForMonitors(
+            Watchdog.getInstance().pauseWatchingMonitorsFor(
                     SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#mount might be slow");
             mVold.mount(vol.id, vol.mountFlags, vol.mountUserId, new IVoldMountCallback.Stub() {
                 @Override
@@ -2472,7 +2472,7 @@
 
         final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
 
-        Watchdog.getInstance().setOneOffTimeoutForMonitors(
+        Watchdog.getInstance().pauseWatchingMonitorsFor(
                 PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow");
         try {
             mVold.partition(diskId, IVold.PARTITION_TYPE_PUBLIC, -1);
@@ -2491,7 +2491,7 @@
 
         final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
 
-        Watchdog.getInstance().setOneOffTimeoutForMonitors(
+        Watchdog.getInstance().pauseWatchingMonitorsFor(
                 PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow");
         try {
             mVold.partition(diskId, IVold.PARTITION_TYPE_PRIVATE, -1);
@@ -2510,7 +2510,7 @@
 
         final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
 
-        Watchdog.getInstance().setOneOffTimeoutForMonitors(
+        Watchdog.getInstance().pauseWatchingMonitorsFor(
                 PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow");
         try {
             mVold.partition(diskId, IVold.PARTITION_TYPE_MIXED, ratio);
@@ -3620,7 +3620,7 @@
 
         @Override
         public ParcelFileDescriptor open() throws AppFuseMountException {
-            Watchdog.getInstance().setOneOffTimeoutForMonitors(
+            Watchdog.getInstance().pauseWatchingMonitorsFor(
                 SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#open might be slow");
             try {
                 final FileDescriptor fd = mVold.mountAppFuse(uid, mountId);
@@ -3634,7 +3634,7 @@
         @Override
         public ParcelFileDescriptor openFile(int mountId, int fileId, int flags)
                 throws AppFuseMountException {
-            Watchdog.getInstance().setOneOffTimeoutForMonitors(
+            Watchdog.getInstance().pauseWatchingMonitorsFor(
                 SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#openFile might be slow");
             try {
                 return new ParcelFileDescriptor(
@@ -3646,7 +3646,7 @@
 
         @Override
         public void close() throws Exception {
-            Watchdog.getInstance().setOneOffTimeoutForMonitors(
+            Watchdog.getInstance().pauseWatchingMonitorsFor(
                 SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#close might be slow");
             if (mMounted) {
                 mVold.unmountAppFuse(uid, mountId);
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 55aa716..003046a 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -250,7 +250,7 @@
         private Monitor mCurrentMonitor;
         private long mStartTimeMillis;
         private int mPauseCount;
-        private long mOneOffTimeoutMillis;
+        private long mPauseEndTimeMillis;
 
         HandlerChecker(Handler handler, String name) {
             mHandler = handler;
@@ -270,20 +270,19 @@
          * @param handlerCheckerTimeoutMillis the timeout to use for this run
          */
         public void scheduleCheckLocked(long handlerCheckerTimeoutMillis) {
-            if (mOneOffTimeoutMillis > 0) {
-              mWaitMaxMillis = mOneOffTimeoutMillis;
-              mOneOffTimeoutMillis = 0;
-            } else {
-              mWaitMaxMillis = handlerCheckerTimeoutMillis;
-            }
+            mWaitMaxMillis = handlerCheckerTimeoutMillis;
 
             if (mCompleted) {
                 // Safe to update monitors in queue, Handler is not in the middle of work
                 mMonitors.addAll(mMonitorQueue);
                 mMonitorQueue.clear();
             }
+
+            long nowMillis = SystemClock.uptimeMillis();
+            boolean isPaused = mPauseCount > 0
+                    || (mPauseEndTimeMillis > 0 && mPauseEndTimeMillis < nowMillis);
             if ((mMonitors.size() == 0 && mHandler.getLooper().getQueue().isPolling())
-                    || (mPauseCount > 0)) {
+                    || isPaused) {
                 // Don't schedule until after resume OR
                 // If the target looper has recently been polling, then
                 // there is no reason to enqueue our checker on it since that
@@ -301,7 +300,8 @@
 
             mCompleted = false;
             mCurrentMonitor = null;
-            mStartTimeMillis = SystemClock.uptimeMillis();
+            mStartTimeMillis = nowMillis;
+            mPauseEndTimeMillis = 0;
             mHandler.postAtFrontOfQueue(this);
         }
 
@@ -360,20 +360,19 @@
         }
 
         /**
-         * Sets the timeout of the HandlerChecker for one run.
+         * Pauses the checks for the given time.
          *
-         * <p>The current run will be ignored and the next run will be set to this timeout.
-         *
-         * <p>If a one off timeout is already set, the maximum timeout will be used.
+         * <p>The current run will be ignored and another run will be scheduled after
+         * the given time.
          */
-        public void setOneOffTimeoutLocked(int temporaryTimeoutMillis, String reason) {
-            mOneOffTimeoutMillis = Math.max(temporaryTimeoutMillis, mOneOffTimeoutMillis);
+        public void pauseForLocked(int pauseMillis, String reason) {
+            mPauseEndTimeMillis = SystemClock.uptimeMillis() + pauseMillis;
             // Mark as completed, because there's a chance we called this after the watchog
             // thread loop called Object#wait after 'WAITED_HALF'. In that case we want to ensure
             // the next call to #getCompletionStateLocked for this checker returns 'COMPLETED'
             mCompleted = true;
-            Slog.i(TAG, "Extending timeout of HandlerChecker: " + mName + " for reason: "
-                    + reason + ". New timeout: " + mOneOffTimeoutMillis);
+            Slog.i(TAG, "Pausing of HandlerChecker: " + mName + " for reason: "
+                    + reason + ". Pause end time: " + mPauseEndTimeMillis);
         }
 
         /** Pause the HandlerChecker. */
@@ -623,34 +622,32 @@
     }
 
      /**
-     * Sets a one-off timeout for the next run of the watchdog for this thread. This is useful
+     * Pauses the checks of the watchdog for this thread. This is useful
      * to run a slow operation on one of the monitored thread.
      *
-     * <p>After the next run, the timeout will go back to the default value.
-     *
-     * <p>If the current thread has not been added to the Watchdog, this call is a no-op.
-     *
-     * <p>If a one-off timeout for the current thread is already, the max value will be used.
+     * <p>After the given time, the timeout will go back to the default value.
+     * <p>This method does not require resume to be called.
      */
-    public void setOneOffTimeoutForCurrentThread(int oneOffTimeoutMillis, String reason) {
+    public void pauseWatchingCurrentThreadFor(int pauseMillis, String reason) {
         synchronized (mLock) {
             for (HandlerCheckerAndTimeout hc : mHandlerCheckers) {
                 HandlerChecker checker = hc.checker();
                 if (Thread.currentThread().equals(checker.getThread())) {
-                    checker.setOneOffTimeoutLocked(oneOffTimeoutMillis, reason);
+                    checker.pauseForLocked(pauseMillis, reason);
                 }
             }
         }
     }
 
     /**
-     * Sets a one-off timeout for the next run of the watchdog for the monitor thread.
+     * Pauses the checks of the watchdog for the monitor thread for the given time
      *
-     * <p>Simiar to {@link setOneOffTimeoutForCurrentThread} but used for monitors added through
-     * {@link #addMonitor}
+     * <p>Similar to {@link pauseWatchingCurrentThreadFor} but used for monitors added
+     * through {@link #addMonitor}
+     * <p>This method does not require resume to be called.
      */
-    public void setOneOffTimeoutForMonitors(int oneOffTimeoutMillis, String reason) {
-        mMonitorChecker.setOneOffTimeoutLocked(oneOffTimeoutMillis, reason);
+    public void pauseWatchingMonitorsFor(int pauseMillis, String reason) {
+        mMonitorChecker.pauseForLocked(pauseMillis, reason);
     }
 
     /**
@@ -664,7 +661,7 @@
      * adds another pause and will require an additional {@link #resumeCurrentThread} to resume.
      *
      * <p>Note: Use with care, as any deadlocks on the current thread will be undetected until all
-     * pauses have been resumed. Prefer to use #setOneOffTimeoutForCurrentThread.
+     * pauses have been resumed. Prefer to use #pauseWatchingCurrentThreadFor.
      */
     public void pauseWatchingCurrentThread(String reason) {
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java b/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java
index 9b5f18c..710278d 100644
--- a/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java
+++ b/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java
@@ -16,6 +16,8 @@
 
 package com.android.server.am;
 
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+
 import android.content.Context;
 import android.content.DialogInterface;
 import android.os.Handler;
@@ -54,6 +56,7 @@
         setButton(DialogInterface.BUTTON_POSITIVE, "Force Close", mHandler.obtainMessage(1, app));
         setTitle("Waiting For Debugger");
         WindowManager.LayoutParams attrs = getWindow().getAttributes();
+        attrs.privateFlags |= SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
         attrs.setTitle("Waiting For Debugger: " + app.info.processName);
         getWindow().setAttributes(attrs);
     }
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index 127c5b3..3c56752 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -1440,10 +1440,9 @@
                     r.curComponent.getPackageName(), r.userId, Event.APP_COMPONENT_USED);
         }
 
-        // Broadcast is being executed, its package can't be stopped.
         try {
-            mService.mPackageManagerInt.setPackageStoppedState(
-                    r.curComponent.getPackageName(), false, r.userId);
+            mService.mPackageManagerInt.notifyComponentUsed(
+                    r.curComponent.getPackageName(), r.userId, r.callerPackage, r.toString());
         } catch (IllegalArgumentException e) {
             Slog.w(TAG, "Failed trying to unstop package "
                     + r.curComponent.getPackageName() + ": " + e);
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index d19eae5..b481697 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -1982,8 +1982,8 @@
         mService.notifyPackageUse(receiverPackageName,
                 PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
 
-        mService.mPackageManagerInt.setPackageStoppedState(
-                receiverPackageName, false, r.userId);
+        mService.mPackageManagerInt.notifyComponentUsed(
+                receiverPackageName, r.userId, r.callerPackage, r.toString());
     }
 
     private void reportUsageStatsBroadcastDispatched(@NonNull ProcessRecord app,
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index a451f36..9bba08a 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2373,8 +2373,7 @@
             }
         }
 
-        if (ppr.getLastProviderTime() > 0
-                && (ppr.getLastProviderTime() + mConstants.CONTENT_PROVIDER_RETAIN_TIME) > now) {
+        if ((ppr.getLastProviderTime() + mConstants.CONTENT_PROVIDER_RETAIN_TIME) > now) {
             if (adj > PREVIOUS_APP_ADJ) {
                 adj = PREVIOUS_APP_ADJ;
                 schedGroup = SCHED_GROUP_BACKGROUND;
diff --git a/services/core/java/com/android/server/am/ProcessProviderRecord.java b/services/core/java/com/android/server/am/ProcessProviderRecord.java
index 751e8a82..9b72a3a 100644
--- a/services/core/java/com/android/server/am/ProcessProviderRecord.java
+++ b/services/core/java/com/android/server/am/ProcessProviderRecord.java
@@ -34,7 +34,7 @@
     /**
      * The last time someone else was using a provider in this process.
      */
-    private long mLastProviderTime;
+    private long mLastProviderTime = Long.MIN_VALUE;
 
     /**
      * class (String) -> ContentProviderRecord.
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index a9c388c..27c0876 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -280,7 +280,7 @@
      * The last time the process was in the TOP state or greater.
      */
     @GuardedBy("mService")
-    private long mLastTopTime;
+    private long mLastTopTime = Long.MIN_VALUE;
 
     /**
      * Is this an empty background process?
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 16e3fdf2..aa788eb 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -118,6 +118,7 @@
     // The list is sorted.
     @VisibleForTesting
     static final String[] sDeviceConfigAconfigScopes = new String[] {
+        "accessibility",
         "android_core_networking",
         "angle",
         "arc_next",
@@ -152,6 +153,7 @@
         "preload_safety",
         "responsible_apis",
         "rust",
+        "safety_center",
         "system_performance",
         "test_suites",
         "text",
@@ -159,7 +161,14 @@
         "tv_system_ui",
         "vibrator",
         "virtual_devices",
+        "wear_calling_messaging",
+        "wear_connectivity",
+        "wear_esim_carriers",
         "wear_frameworks",
+        "wear_health_services",
+        "wear_media",
+        "wear_offload",
+        "wear_security",
         "wear_system_health",
         "wear_systems",
         "window_surfaces",
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 26d99d8..cbaf05b 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -2,7 +2,7 @@
 
 flag {
     name: "oomadjuster_correctness_rewrite"
-    namespace: "android_platform_power_optimization"
+    namespace: "backstage_power"
     description: "Utilize new OomAdjuster implementation"
     bug: "298055811"
     is_fixed_read_only: true
@@ -15,3 +15,10 @@
      description: "Feature flag for the ANR timer service"
      bug: "282428924"
 }
+
+flag {
+    name: "fgs_abuse_detection"
+    namespace: "backstage_power"
+    description: "Detect abusive FGS behavior for certain types (camera, mic, media, location)."
+    bug: "295545575"
+}
diff --git a/services/core/java/com/android/server/audio/AdiDeviceState.java b/services/core/java/com/android/server/audio/AdiDeviceState.java
index ba43c8d..292fc14 100644
--- a/services/core/java/com/android/server/audio/AdiDeviceState.java
+++ b/services/core/java/com/android/server/audio/AdiDeviceState.java
@@ -188,7 +188,7 @@
      * {@link AdiDeviceState#toPersistableString()}.
      */
     public static int getPeristedMaxSize() {
-        return 36;  /* (mDeviceType)2 + (mDeviceAddresss)17 + (mInternalDeviceType)9 + (mSAEnabled)1
+        return 36;  /* (mDeviceType)2 + (mDeviceAddress)17 + (mInternalDeviceType)9 + (mSAEnabled)1
                            + (mHasHeadTracker)1 + (mHasHeadTrackerEnabled)1
                            + (SETTINGS_FIELD_SEPARATOR)5 */
     }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 9cfac9a..eea3d38 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -68,6 +68,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.NoSuchElementException;
+import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -496,8 +497,9 @@
             AudioDeviceInfo.TYPE_AUX_LINE
     };
 
-    /*package */ static boolean isValidCommunicationDevice(AudioDeviceInfo device) {
-        return isValidCommunicationDeviceType(device.getType());
+    /*package */ static boolean isValidCommunicationDevice(@NonNull AudioDeviceInfo device) {
+        Objects.requireNonNull(device, "device must not be null");
+        return device.isSink() && isValidCommunicationDeviceType(device.getType());
     }
 
     private static boolean isValidCommunicationDeviceType(int deviceType) {
diff --git a/services/core/java/com/android/server/audio/MusicFxHelper.java b/services/core/java/com/android/server/audio/MusicFxHelper.java
index 6c0fef5..5f4e4c3 100644
--- a/services/core/java/com/android/server/audio/MusicFxHelper.java
+++ b/services/core/java/com/android/server/audio/MusicFxHelper.java
@@ -157,7 +157,8 @@
             Log.w(TAG, " inside handle MSG_EFFECT_CLIENT_GONE");
             // Once the uid is no longer running, close all remain audio session(s) for this UID
             if (mClientUidSessionMap.get(Integer.valueOf(uid)) != null) {
-                final List<Integer> sessions = mClientUidSessionMap.get(Integer.valueOf(uid));
+                final List<Integer> sessions =
+                        new ArrayList(mClientUidSessionMap.get(Integer.valueOf(uid)));
                 Log.i(TAG, "UID " + uid + " gone, closing " + sessions.size() + " sessions");
                 for (Integer session : sessions) {
                     Intent intent = new Intent(
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 35260ed..7abd9c7 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -39,10 +39,9 @@
 import android.media.ISpatializerHeadTrackingModeCallback;
 import android.media.ISpatializerOutputCallback;
 import android.media.MediaMetrics;
-import android.media.SpatializationLevel;
-import android.media.SpatializationMode;
 import android.media.Spatializer;
-import android.media.SpatializerHeadTrackingMode;
+import android.media.audio.common.HeadTracking;
+import android.media.audio.common.Spatialization;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.text.TextUtils;
@@ -84,22 +83,22 @@
 
     /*package*/ static final SparseIntArray SPAT_MODE_FOR_DEVICE_TYPE = new SparseIntArray(14) {
         {
-            append(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, SpatializationMode.SPATIALIZER_TRANSAURAL);
-            append(AudioDeviceInfo.TYPE_WIRED_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL);
-            append(AudioDeviceInfo.TYPE_WIRED_HEADPHONES, SpatializationMode.SPATIALIZER_BINAURAL);
+            append(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, Spatialization.Mode.TRANSAURAL);
+            append(AudioDeviceInfo.TYPE_WIRED_HEADSET, Spatialization.Mode.BINAURAL);
+            append(AudioDeviceInfo.TYPE_WIRED_HEADPHONES, Spatialization.Mode.BINAURAL);
             // assumption for A2DP: mostly headsets
-            append(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, SpatializationMode.SPATIALIZER_BINAURAL);
-            append(AudioDeviceInfo.TYPE_DOCK, SpatializationMode.SPATIALIZER_TRANSAURAL);
-            append(AudioDeviceInfo.TYPE_USB_ACCESSORY, SpatializationMode.SPATIALIZER_TRANSAURAL);
-            append(AudioDeviceInfo.TYPE_USB_DEVICE, SpatializationMode.SPATIALIZER_TRANSAURAL);
-            append(AudioDeviceInfo.TYPE_USB_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL);
-            append(AudioDeviceInfo.TYPE_LINE_ANALOG, SpatializationMode.SPATIALIZER_TRANSAURAL);
-            append(AudioDeviceInfo.TYPE_LINE_DIGITAL, SpatializationMode.SPATIALIZER_TRANSAURAL);
-            append(AudioDeviceInfo.TYPE_AUX_LINE, SpatializationMode.SPATIALIZER_TRANSAURAL);
-            append(AudioDeviceInfo.TYPE_BLE_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL);
-            append(AudioDeviceInfo.TYPE_BLE_SPEAKER, SpatializationMode.SPATIALIZER_TRANSAURAL);
+            append(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, Spatialization.Mode.BINAURAL);
+            append(AudioDeviceInfo.TYPE_DOCK, Spatialization.Mode.TRANSAURAL);
+            append(AudioDeviceInfo.TYPE_USB_ACCESSORY, Spatialization.Mode.TRANSAURAL);
+            append(AudioDeviceInfo.TYPE_USB_DEVICE, Spatialization.Mode.TRANSAURAL);
+            append(AudioDeviceInfo.TYPE_USB_HEADSET, Spatialization.Mode.BINAURAL);
+            append(AudioDeviceInfo.TYPE_LINE_ANALOG, Spatialization.Mode.TRANSAURAL);
+            append(AudioDeviceInfo.TYPE_LINE_DIGITAL, Spatialization.Mode.TRANSAURAL);
+            append(AudioDeviceInfo.TYPE_AUX_LINE, Spatialization.Mode.TRANSAURAL);
+            append(AudioDeviceInfo.TYPE_BLE_HEADSET, Spatialization.Mode.BINAURAL);
+            append(AudioDeviceInfo.TYPE_BLE_SPEAKER, Spatialization.Mode.TRANSAURAL);
             // assumption that BLE broadcast would be mostly consumed on headsets
-            append(AudioDeviceInfo.TYPE_BLE_BROADCAST, SpatializationMode.SPATIALIZER_BINAURAL);
+            append(AudioDeviceInfo.TYPE_BLE_BROADCAST, Spatialization.Mode.BINAURAL);
         }
     };
 
@@ -226,12 +225,12 @@
                 ArrayList<Integer> list = new ArrayList<>(0);
                 for (byte value : values) {
                     switch (value) {
-                        case SpatializerHeadTrackingMode.OTHER:
-                        case SpatializerHeadTrackingMode.DISABLED:
+                        case HeadTracking.Mode.OTHER:
+                        case HeadTracking.Mode.DISABLED:
                             // not expected here, skip
                             break;
-                        case SpatializerHeadTrackingMode.RELATIVE_WORLD:
-                        case SpatializerHeadTrackingMode.RELATIVE_SCREEN:
+                        case HeadTracking.Mode.RELATIVE_WORLD:
+                        case HeadTracking.Mode.RELATIVE_SCREEN:
                             list.add(headTrackingModeTypeToSpatializerInt(value));
                             break;
                         default:
@@ -254,10 +253,10 @@
             byte[] spatModes = spat.getSupportedModes();
             for (byte mode : spatModes) {
                 switch (mode) {
-                    case SpatializationMode.SPATIALIZER_BINAURAL:
+                    case Spatialization.Mode.BINAURAL:
                         mBinauralSupported = true;
                         break;
-                    case SpatializationMode.SPATIALIZER_TRANSAURAL:
+                    case Spatialization.Mode.TRANSAURAL:
                         mTransauralSupported = true;
                         break;
                     default:
@@ -274,8 +273,8 @@
             // initialize list of compatible devices
             for (int i = 0; i < SPAT_MODE_FOR_DEVICE_TYPE.size(); i++) {
                 int mode = SPAT_MODE_FOR_DEVICE_TYPE.valueAt(i);
-                if ((mode == (int) SpatializationMode.SPATIALIZER_BINAURAL && mBinauralSupported)
-                        || (mode == (int) SpatializationMode.SPATIALIZER_TRANSAURAL
+                if ((mode == (int) Spatialization.Mode.BINAURAL && mBinauralSupported)
+                        || (mode == (int) Spatialization.Mode.TRANSAURAL
                             && mTransauralSupported)) {
                     mSACapableDeviceTypes.add(SPAT_MODE_FOR_DEVICE_TYPE.keyAt(i));
                 }
@@ -577,9 +576,9 @@
 
         int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(device.getDeviceType(),
                 Integer.MIN_VALUE);
-        device.setSAEnabled(spatMode == SpatializationMode.SPATIALIZER_BINAURAL
+        device.setSAEnabled(spatMode == Spatialization.Mode.BINAURAL
                 ? mBinauralEnabledDefault
-                : spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL
+                : spatMode == Spatialization.Mode.TRANSAURAL
                         ? mTransauralEnabledDefault
                         : false);
         device.setHeadTrackerEnabled(mHeadTrackingEnabledDefault);
@@ -629,9 +628,9 @@
         if (isBluetoothDevice(internalDeviceType)) return deviceType;
 
         final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE);
-        if (spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL) {
+        if (spatMode == Spatialization.Mode.TRANSAURAL) {
             return AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
-        } else if (spatMode == SpatializationMode.SPATIALIZER_BINAURAL) {
+        } else if (spatMode == Spatialization.Mode.BINAURAL) {
             return AudioDeviceInfo.TYPE_WIRED_HEADPHONES;
         }
         return AudioDeviceInfo.TYPE_UNKNOWN;
@@ -690,8 +689,7 @@
             // since their physical characteristics are unknown
             if (deviceState.getAudioDeviceCategory() == AUDIO_DEVICE_CATEGORY_UNKNOWN
                     || deviceState.getAudioDeviceCategory() == AUDIO_DEVICE_CATEGORY_HEADPHONES) {
-                available = (spatMode == SpatializationMode.SPATIALIZER_BINAURAL)
-                        && mBinauralSupported;
+                available = (spatMode == Spatialization.Mode.BINAURAL) && mBinauralSupported;
             } else {
                 available = false;
             }
@@ -804,8 +802,8 @@
         // not be included.
         final byte modeForDevice = (byte) SPAT_MODE_FOR_DEVICE_TYPE.get(ada.getType(),
                 /*default when type not found*/ -1);
-        if ((modeForDevice == SpatializationMode.SPATIALIZER_BINAURAL && mBinauralSupported)
-                || (modeForDevice == SpatializationMode.SPATIALIZER_TRANSAURAL
+        if ((modeForDevice == Spatialization.Mode.BINAURAL && mBinauralSupported)
+                || (modeForDevice == Spatialization.Mode.TRANSAURAL
                         && mTransauralSupported)) {
             return true;
         }
@@ -1479,7 +1477,7 @@
     }
 
     synchronized void onInitSensors() {
-        final boolean init = mFeatureEnabled && (mSpatLevel != SpatializationLevel.NONE);
+        final boolean init = mFeatureEnabled && (mSpatLevel != Spatialization.Level.NONE);
         final String action = init ? "initializing" : "releasing";
         if (mSpat == null) {
             logloge("not " + action + " sensors, null spatializer");
@@ -1545,13 +1543,13 @@
     // SDK <-> AIDL converters
     private static int headTrackingModeTypeToSpatializerInt(byte mode) {
         switch (mode) {
-            case SpatializerHeadTrackingMode.OTHER:
+            case HeadTracking.Mode.OTHER:
                 return Spatializer.HEAD_TRACKING_MODE_OTHER;
-            case SpatializerHeadTrackingMode.DISABLED:
+            case HeadTracking.Mode.DISABLED:
                 return Spatializer.HEAD_TRACKING_MODE_DISABLED;
-            case SpatializerHeadTrackingMode.RELATIVE_WORLD:
+            case HeadTracking.Mode.RELATIVE_WORLD:
                 return Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD;
-            case SpatializerHeadTrackingMode.RELATIVE_SCREEN:
+            case HeadTracking.Mode.RELATIVE_SCREEN:
                 return Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE;
             default:
                 throw (new IllegalArgumentException("Unexpected head tracking mode:" + mode));
@@ -1561,13 +1559,13 @@
     private static byte spatializerIntToHeadTrackingModeType(int sdkMode) {
         switch (sdkMode) {
             case Spatializer.HEAD_TRACKING_MODE_OTHER:
-                return SpatializerHeadTrackingMode.OTHER;
+                return HeadTracking.Mode.OTHER;
             case Spatializer.HEAD_TRACKING_MODE_DISABLED:
-                return SpatializerHeadTrackingMode.DISABLED;
+                return HeadTracking.Mode.DISABLED;
             case Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD:
-                return SpatializerHeadTrackingMode.RELATIVE_WORLD;
+                return HeadTracking.Mode.RELATIVE_WORLD;
             case Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE:
-                return SpatializerHeadTrackingMode.RELATIVE_SCREEN;
+                return HeadTracking.Mode.RELATIVE_SCREEN;
             default:
                 throw (new IllegalArgumentException("Unexpected head tracking mode:" + sdkMode));
         }
@@ -1575,11 +1573,11 @@
 
     private static int spatializationLevelToSpatializerInt(byte level) {
         switch (level) {
-            case SpatializationLevel.NONE:
+            case Spatialization.Level.NONE:
                 return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
-            case SpatializationLevel.SPATIALIZER_MULTICHANNEL:
+            case Spatialization.Level.MULTICHANNEL:
                 return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL;
-            case SpatializationLevel.SPATIALIZER_MCHAN_BED_PLUS_OBJECTS:
+            case Spatialization.Level.BED_PLUS_OBJECTS:
                 return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MCHAN_BED_PLUS_OBJECTS;
             default:
                 throw (new IllegalArgumentException("Unexpected spatializer level:" + level));
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 3d347be..f9bc8dc 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -53,6 +53,8 @@
 import android.hardware.usb.UsbManager;
 import android.media.AudioManager;
 import android.nfc.INfcAdapter;
+import android.nfc.NfcAdapter;
+import android.nfc.NfcManager;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.HandlerExecutor;
@@ -163,10 +165,6 @@
      * SCALER_ROTATE_AND_CROP_NONE  -> Always return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE
      */
 
-    // Flags arguments to NFC adapter to enable/disable NFC
-    public static final int DISABLE_POLLING_FLAGS = 0x1000;
-    public static final int ENABLE_POLLING_FLAGS = 0x0000;
-
     // Handler message codes
     private static final int MSG_SWITCH_USER = 1;
     private static final int MSG_NOTIFY_DEVICE_STATE = 2;
@@ -216,7 +214,6 @@
     private final List<CameraUsageEvent> mCameraUsageHistory = new ArrayList<>();
 
     private static final String NFC_NOTIFICATION_PROP = "ro.camera.notify_nfc";
-    private static final String NFC_SERVICE_BINDER_NAME = "nfc";
     private static final IBinder nfcInterfaceToken = new Binder();
 
     private final boolean mNotifyNfc;
@@ -1274,8 +1271,13 @@
         }
     }
 
-    private void notifyNfcService(boolean enablePolling) {
-
+    // TODO(b/303286040): Remove the raw INfcAdapter usage once |ENABLE_NFC_MAINLINE_FLAG| is
+    // rolled out.
+    private static final String NFC_SERVICE_BINDER_NAME = "nfc";
+    // Flags arguments to NFC adapter to enable/disable NFC
+    public static final int DISABLE_POLLING_FLAGS = 0x1000;
+    public static final int ENABLE_POLLING_FLAGS = 0x0000;
+    private void setNfcReaderModeUsingINfcAdapter(boolean enablePolling) {
         IBinder nfcServiceBinder = getBinderService(NFC_SERVICE_BINDER_NAME);
         if (nfcServiceBinder == null) {
             Slog.w(TAG, "Could not connect to NFC service to notify it of camera state");
@@ -1291,6 +1293,25 @@
         }
     }
 
+    private void notifyNfcService(boolean enablePolling) {
+        if (android.nfc.Flags.enableNfcMainline()) {
+            NfcManager nfcManager = mContext.getSystemService(NfcManager.class);
+            if (nfcManager == null) {
+                Slog.w(TAG, "Could not connect to NFC service to notify it of camera state");
+                return;
+            }
+            NfcAdapter nfcAdapter = nfcManager.getDefaultAdapter();
+            if (nfcAdapter == null) {
+                Slog.w(TAG, "Could not connect to NFC service to notify it of camera state");
+                return;
+            }
+            if (DEBUG) Slog.v(TAG, "Setting NFC reader mode. enablePolling: " + enablePolling);
+            nfcAdapter.setReaderMode(enablePolling);
+        } else {
+            setNfcReaderModeUsingINfcAdapter(enablePolling);
+        }
+    }
+
     private static int[] toArray(Collection<Integer> c) {
         int len = c.size();
         int[] ret = new int[len];
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 906c66d..76dde54 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -1474,11 +1474,11 @@
                                 .getDrawable(R.drawable.ic_safety_protection);
                         toastToShow = Toast.makeCustomToastWithIcon(toastContext,
                                 UiThread.get().getLooper(), message,
-                                Toast.LENGTH_SHORT, safetyProtectionIcon);
+                                Toast.LENGTH_LONG, safetyProtectionIcon);
                     } else {
                         toastToShow = Toast.makeText(
                                 toastContext, UiThread.get().getLooper(), message,
-                                Toast.LENGTH_SHORT);
+                                Toast.LENGTH_LONG);
                     }
                     toastToShow.show();
                 }
diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
index 425a1af..6695801 100644
--- a/services/core/java/com/android/server/display/DisplayBrightnessState.java
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -27,6 +27,8 @@
  * the DisplayBrightnessModeStrategies when updating the brightness.
  */
 public final class DisplayBrightnessState {
+    public static final float CUSTOM_ANIMATION_RATE_NOT_SET = -1f;
+
     private final float mBrightness;
     private final float mSdrBrightness;
 
@@ -37,6 +39,8 @@
 
     private final boolean mIsSlowChange;
 
+    private final float mCustomAnimationRate;
+
     private DisplayBrightnessState(Builder builder) {
         mBrightness = builder.getBrightness();
         mSdrBrightness = builder.getSdrBrightness();
@@ -45,6 +49,7 @@
         mShouldUseAutoBrightness = builder.getShouldUseAutoBrightness();
         mIsSlowChange = builder.isSlowChange();
         mMaxBrightness = builder.getMaxBrightness();
+        mCustomAnimationRate = builder.getCustomAnimationRate();
     }
 
     /**
@@ -97,7 +102,12 @@
         return mMaxBrightness;
     }
 
-
+    /**
+     * @return custom animation rate
+     */
+    public float getCustomAnimationRate() {
+        return mCustomAnimationRate;
+    }
 
     @Override
     public String toString() {
@@ -112,6 +122,7 @@
         stringBuilder.append(getShouldUseAutoBrightness());
         stringBuilder.append("\n    isSlowChange:").append(mIsSlowChange);
         stringBuilder.append("\n    maxBrightness:").append(mMaxBrightness);
+        stringBuilder.append("\n    customAnimationRate:").append(mCustomAnimationRate);
         return stringBuilder.toString();
     }
 
@@ -137,13 +148,14 @@
                         otherState.getDisplayBrightnessStrategyName())
                 && mShouldUseAutoBrightness == otherState.getShouldUseAutoBrightness()
                 && mIsSlowChange == otherState.isSlowChange()
-                && mMaxBrightness == otherState.getMaxBrightness();
+                && mMaxBrightness == otherState.getMaxBrightness()
+                && mCustomAnimationRate == otherState.getCustomAnimationRate();
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mBrightness, mSdrBrightness, mBrightnessReason,
-                mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness);
+                mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness, mCustomAnimationRate);
     }
 
     /**
@@ -164,6 +176,7 @@
         private boolean mShouldUseAutoBrightness;
         private boolean mIsSlowChange;
         private float mMaxBrightness;
+        private float mCustomAnimationRate = CUSTOM_ANIMATION_RATE_NOT_SET;
 
         /**
          * Create a builder starting with the values from the specified {@link
@@ -180,6 +193,7 @@
             builder.setShouldUseAutoBrightness(state.getShouldUseAutoBrightness());
             builder.setIsSlowChange(state.isSlowChange());
             builder.setMaxBrightness(state.getMaxBrightness());
+            builder.setCustomAnimationRate(state.getCustomAnimationRate());
             return builder;
         }
 
@@ -303,6 +317,22 @@
             return mMaxBrightness;
         }
 
+
+        /**
+         * See {@link DisplayBrightnessState#getCustomAnimationRate()}.
+         */
+        public Builder setCustomAnimationRate(float animationRate) {
+            this.mCustomAnimationRate = animationRate;
+            return this;
+        }
+
+        /**
+         * See {@link DisplayBrightnessState#getCustomAnimationRate()}.
+         */
+        public float getCustomAnimationRate() {
+            return mCustomAnimationRate;
+        }
+
         /**
          * This is used to construct an immutable DisplayBrightnessState object from its builder
          */
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 5b87eea..e5f01df 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -586,7 +586,8 @@
         mSystemReady = false;
         mConfigParameterProvider = new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
         mExtraDisplayLoggingPackageName = DisplayProperties.debug_vri_package().orElse(null);
-        mExtraDisplayEventLogging = !TextUtils.isEmpty(mExtraDisplayLoggingPackageName);
+        // TODO: b/306170135 - return TextUtils package name check instead
+        mExtraDisplayEventLogging = true;
     }
 
     public void setupSchedulerPolicies() {
@@ -2306,8 +2307,10 @@
 
     @GuardedBy("mSyncRoot")
     private boolean hdrConversionIntroducesLatencyLocked() {
+        HdrConversionMode mode = getHdrConversionModeSettingInternal();
         final int preferredHdrOutputType =
-                getHdrConversionModeSettingInternal().getPreferredHdrOutputType();
+                mode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM
+                        ? mSystemPreferredHdrOutputType : mode.getPreferredHdrOutputType();
         if (preferredHdrOutputType != Display.HdrCapabilities.HDR_TYPE_INVALID) {
             int[] hdrTypesWithLatency = mInjector.getHdrOutputTypesWithLatency();
             return ArrayUtils.contains(hdrTypesWithLatency, preferredHdrOutputType);
@@ -2588,16 +2591,14 @@
             // TODO(b/202378408) set minimal post-processing only if it's supported once we have a
             // separate API for disabling on-device processing.
             boolean mppRequest = isMinimalPostProcessingAllowed() && preferMinimalPostProcessing;
-            boolean disableHdrConversionForLatency = false;
+            // If HDR conversion introduces latency, disable that in case minimal
+            // post-processing is requested
+            boolean disableHdrConversionForLatency =
+                    mppRequest ? hdrConversionIntroducesLatencyLocked() : false;
 
             if (display.getRequestedMinimalPostProcessingLocked() != mppRequest) {
                 display.setRequestedMinimalPostProcessingLocked(mppRequest);
                 shouldScheduleTraversal = true;
-                // If HDR conversion introduces latency, disable that in case minimal
-                // post-processing is requested
-                if (mppRequest) {
-                    disableHdrConversionForLatency = hdrConversionIntroducesLatencyLocked();
-                }
             }
 
             if (shouldScheduleTraversal) {
@@ -2933,8 +2934,15 @@
     // Send a display event if the display is enabled
     private void sendDisplayEventIfEnabledLocked(@NonNull LogicalDisplay display,
                                                  @DisplayEvent int event) {
+        final boolean displayIsEnabled = display.isEnabledLocked();
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+            Trace.instant(Trace.TRACE_TAG_POWER,
+                    "sendDisplayEventLocked#event=" + event + ",displayEnabled="
+                            + displayIsEnabled);
+        }
+
         // Only send updates outside of DisplayManagerService for enabled displays
-        if (display.isEnabledLocked()) {
+        if (displayIsEnabled) {
             sendDisplayEventLocked(display, event);
         } else if (mExtraDisplayEventLogging) {
             Slog.i(TAG, "Not Sending Display Event; display is not enabled: " + display);
@@ -2974,7 +2982,7 @@
 
     // Check if the target app is in cached mode
     private boolean isUidCached(int uid) {
-        if (mActivityManagerInternal == null) {
+        if (mActivityManagerInternal == null || uid < FIRST_APPLICATION_UID) {
             return false;
         }
         int procState = mActivityManagerInternal.getUidProcessState(uid);
@@ -2991,7 +2999,11 @@
                     + displayId + ", event=" + event
                     + (uids != null ? ", uids=" + uids : ""));
         }
-
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+            Trace.instant(Trace.TRACE_TAG_POWER,
+                    "deliverDisplayEvent#event=" + event + ",displayId="
+                            + displayId   + (uids != null ? ", uids=" + uids : ""));
+        }
         // Grab the lock and copy the callbacks.
         final int count;
         synchronized (mSyncRoot) {
@@ -3031,7 +3043,8 @@
     }
 
     private boolean extraLogging(String packageName) {
-        return mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals(packageName);
+        // TODO: b/306170135 - return mExtraDisplayLoggingPackageName & package name check instead
+        return true;
     }
 
     // Runs on Handler thread.
@@ -3498,10 +3511,13 @@
 
         @Override
         public void binderDied() {
-            if (DEBUG || mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals(
-                    mPackageName)) {
+            if (DEBUG || extraLogging(mPackageName)) {
                 Slog.d(TAG, "Display listener for pid " + mPid + " died.");
             }
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+                Trace.instant(Trace.TRACE_TAG_POWER,
+                        "displayManagerBinderDied#mPid=" + mPid);
+            }
             onCallbackDied(this);
         }
 
@@ -3510,11 +3526,15 @@
          */
         public boolean notifyDisplayEventAsync(int displayId, @DisplayEvent int event) {
             if (!shouldSendEvent(event)) {
-                if (mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals(
-                        mPackageName)) {
+                if (extraLogging(mPackageName)) {
                     Slog.i(TAG,
                             "Not sending displayEvent: " + event + " due to mask:" + mEventsMask);
                 }
+                if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+                    Trace.instant(Trace.TRACE_TAG_POWER,
+                            "notifyDisplayEventAsync#notSendingEvent=" + event + ",mEventsMask="
+                                    + mEventsMask);
+                }
                 return true;
             }
 
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 7d9c018..0f00027 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -561,8 +561,9 @@
                         brightnessSetting, () -> postBrightnessChangeRunnable(),
                         new HandlerExecutor(mHandler));
 
-        mBrightnessClamperController = new BrightnessClamperController(mHandler,
-                modeChangeCallback::run, new BrightnessClamperController.DisplayDeviceData(
+        mBrightnessClamperController = mInjector.getBrightnessClamperController(
+                mHandler, modeChangeCallback::run,
+                new BrightnessClamperController.DisplayDeviceData(
                 mUniqueDisplayId,
                 mThermalBrightnessThrottlingDataId,
                 logicalDisplay.getPowerThrottlingDataIdLocked(),
@@ -1353,6 +1354,8 @@
         float rawBrightnessState = displayBrightnessState.getBrightness();
         mBrightnessReasonTemp.set(displayBrightnessState.getBrightnessReason());
         boolean slowChange = displayBrightnessState.isSlowChange();
+        // custom transition duration
+        float customAnimationRate = displayBrightnessState.getCustomAnimationRate();
 
         // Set up the ScreenOff controller used when coming out of SCREEN_OFF and the ALS sensor
         // doesn't yet have a valid lux value to use with auto-brightness.
@@ -1485,6 +1488,9 @@
 
         brightnessState = clampedState.getBrightness();
         slowChange = clampedState.isSlowChange();
+        // faster rate wins, at this point customAnimationRate == -1, strategy does not control
+        // customAnimationRate. Should be revisited if strategy start setting this value
+        customAnimationRate = Math.max(customAnimationRate, clampedState.getCustomAnimationRate());
         mBrightnessReasonTemp.addModifier(clampedState.getBrightnessReason().getModifier());
 
         if (updateScreenBrightnessSetting) {
@@ -1553,9 +1559,6 @@
             // allowed range.
             float animateValue = clampScreenBrightness(brightnessState);
 
-            // custom transition duration
-            float customTransitionRate = -1f;
-
             // If there are any HDR layers on the screen, we have a special brightness value that we
             // use instead. We still preserve the calculated brightness for Standard Dynamic Range
             // (SDR) layers, but the main brightness value will be the one for HDR.
@@ -1570,10 +1573,21 @@
                 // We want to scale HDR brightness level with the SDR level, we also need to restore
                 // SDR brightness immediately when entering dim or low power mode.
                 animateValue = mBrightnessRangeController.getHdrBrightnessValue();
-                customTransitionRate = mBrightnessRangeController.getHdrTransitionRate();
+                customAnimationRate = Math.max(customAnimationRate,
+                        mBrightnessRangeController.getHdrTransitionRate());
                 mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_HDR);
             }
 
+            // if doze or suspend state is requested, we want to finish brightnes animation fast
+            // to allow state animation to start
+            if (mPowerRequest.policy == DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE
+                    && (mPowerRequest.dozeScreenState == Display.STATE_UNKNOWN  // dozing
+                    || mPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND
+                    || mPowerRequest.dozeScreenState == Display.STATE_ON_SUSPEND)) {
+                customAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
+                slowChange = false;
+            }
+
             final float currentBrightness = mPowerState.getScreenBrightness();
             final float currentSdrBrightness = mPowerState.getSdrScreenBrightness();
 
@@ -1601,9 +1615,9 @@
                 if (skipAnimation) {
                     animateScreenBrightness(animateValue, sdrAnimateValue,
                             SCREEN_ANIMATION_RATE_MINIMUM);
-                } else if (customTransitionRate > 0) {
+                } else if (customAnimationRate > 0) {
                     animateScreenBrightness(animateValue, sdrAnimateValue,
-                            customTransitionRate, /* ignoreAnimationLimits = */true);
+                            customAnimationRate, /* ignoreAnimationLimits = */true);
                 } else {
                     boolean isIncreasing = animateValue > currentBrightness;
                     final float rampSpeed;
@@ -3059,6 +3073,15 @@
                     modeChangeCallback, displayDeviceConfig, handler, flags, displayToken, info);
         }
 
+        BrightnessClamperController getBrightnessClamperController(Handler handler,
+                BrightnessClamperController.ClamperChangeListener clamperChangeListener,
+                BrightnessClamperController.DisplayDeviceData data, Context context,
+                DisplayManagerFlags flags) {
+
+            return new BrightnessClamperController(handler, clamperChangeListener, data, context,
+                    flags);
+        }
+
         DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
                 SensorManager sensorManager, Resources resources) {
             return DisplayWhiteBalanceFactory.create(handler,
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
index 68f72d3..dfcda40 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
@@ -19,6 +19,8 @@
 import android.annotation.NonNull;
 import android.os.PowerManager;
 
+import com.android.server.display.DisplayBrightnessState;
+
 import java.io.PrintWriter;
 
 /**
@@ -33,6 +35,10 @@
         return mBrightnessCap;
     }
 
+    float getCustomAnimationRate() {
+        return DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
+    }
+
     boolean isActive() {
         return mIsActive;
     }
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index 787f786..b574919 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -51,6 +51,7 @@
  */
 public class BrightnessClamperController {
     private static final String TAG = "BrightnessClamperController";
+
     private final DeviceConfigParameterProvider mDeviceConfigParameterProvider;
     private final Handler mHandler;
     private final ClamperChangeListener mClamperChangeListenerExternal;
@@ -60,6 +61,8 @@
     private final List<BrightnessModifier> mModifiers;
     private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener;
     private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
+
+    private float mCustomAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
     @Nullable
     private Type mClamperType = null;
     private boolean mClamperApplied = false;
@@ -113,6 +116,7 @@
         builder.setIsSlowChange(slowChange);
         builder.setBrightness(cappedBrightness);
         builder.setMaxBrightness(mBrightnessCap);
+        builder.setCustomAnimationRate(mCustomAnimationRate);
 
         if (mClamperType != null) {
             builder.getBrightnessReason().addModifier(BrightnessReason.MODIFIER_THROTTLED);
@@ -182,6 +186,7 @@
     private void recalculateBrightnessCap() {
         float brightnessCap = PowerManager.BRIGHTNESS_MAX;
         Type clamperType = null;
+        float customAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
 
         BrightnessClamper<?> minClamper = mClampers.stream()
                 .filter(BrightnessClamper::isActive)
@@ -191,11 +196,14 @@
         if (minClamper != null) {
             brightnessCap = minClamper.getBrightnessCap();
             clamperType = minClamper.getType();
+            customAnimationRate = minClamper.getCustomAnimationRate();
         }
 
-        if (mBrightnessCap != brightnessCap || mClamperType != clamperType) {
+        if (mBrightnessCap != brightnessCap || mClamperType != clamperType
+                || mCustomAnimationRate != customAnimationRate) {
             mBrightnessCap = brightnessCap;
             mClamperType = clamperType;
+            mCustomAnimationRate = customAnimationRate;
             mClamperChangeListenerExternal.onChanged();
         }
 
diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
index 39f0b13..200d88a 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
@@ -24,7 +24,6 @@
 import android.view.SurfaceControlHdrLayerInfoListener;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.display.BrightnessUtils;
 import com.android.server.display.config.HdrBrightnessData;
 
 import java.io.PrintWriter;
@@ -176,21 +175,14 @@
         } else if (mDesiredMaxBrightness != expectedMaxBrightness) {
             mDesiredMaxBrightness = expectedMaxBrightness;
             long debounceTime;
-            long transitionDuration;
             if (mDesiredMaxBrightness > mMaxBrightness) {
                 debounceTime = mHdrBrightnessData.mBrightnessIncreaseDebounceMillis;
-                transitionDuration = mHdrBrightnessData.mBrightnessIncreaseDurationMillis;
+                mDesiredTransitionRate = mHdrBrightnessData.mScreenBrightnessRampIncrease;
             } else {
                 debounceTime = mHdrBrightnessData.mBrightnessDecreaseDebounceMillis;
-                transitionDuration = mHdrBrightnessData.mBrightnessDecreaseDurationMillis;
+                mDesiredTransitionRate = mHdrBrightnessData.mScreenBrightnessRampDecrease;
             }
 
-            float maxHlg = BrightnessUtils.convertLinearToGamma(mMaxBrightness);
-            float desiredMaxHlg = BrightnessUtils.convertLinearToGamma(mDesiredMaxBrightness);
-
-            mDesiredTransitionRate = Math.abs(
-                    (maxHlg - desiredMaxHlg) * 1000f / transitionDuration);
-
             mHandler.removeCallbacks(mDebouncer);
             mHandler.postDelayed(mDebouncer, debounceTime);
         }
diff --git a/services/core/java/com/android/server/display/config/HdrBrightnessData.java b/services/core/java/com/android/server/display/config/HdrBrightnessData.java
index 48d671d..837fbf7 100644
--- a/services/core/java/com/android/server/display/config/HdrBrightnessData.java
+++ b/services/core/java/com/android/server/display/config/HdrBrightnessData.java
@@ -40,9 +40,9 @@
     public final long mBrightnessIncreaseDebounceMillis;
 
     /**
-     * Brightness increase animation duration
+     * Brightness increase animation speed
      */
-    public final long mBrightnessIncreaseDurationMillis;
+    public final float mScreenBrightnessRampIncrease;
 
     /**
      * Debounce time for brightness decrease
@@ -50,19 +50,19 @@
     public final long mBrightnessDecreaseDebounceMillis;
 
     /**
-     * Brightness decrease animation duration
+     * Brightness decrease animation speed
      */
-    public final long mBrightnessDecreaseDurationMillis;
+    public final float mScreenBrightnessRampDecrease;
 
     @VisibleForTesting
     public HdrBrightnessData(Map<Float, Float> maxBrightnessLimits,
-            long brightnessIncreaseDebounceMillis, long brightnessIncreaseDurationMillis,
-            long brightnessDecreaseDebounceMillis, long brightnessDecreaseDurationMillis) {
+            long brightnessIncreaseDebounceMillis, float screenBrightnessRampIncrease,
+            long brightnessDecreaseDebounceMillis, float screenBrightnessRampDecrease) {
         mMaxBrightnessLimits = maxBrightnessLimits;
         mBrightnessIncreaseDebounceMillis = brightnessIncreaseDebounceMillis;
-        mBrightnessIncreaseDurationMillis = brightnessIncreaseDurationMillis;
+        mScreenBrightnessRampIncrease = screenBrightnessRampIncrease;
         mBrightnessDecreaseDebounceMillis = brightnessDecreaseDebounceMillis;
-        mBrightnessDecreaseDurationMillis = brightnessDecreaseDurationMillis;
+        mScreenBrightnessRampDecrease = screenBrightnessRampDecrease;
     }
 
     @Override
@@ -70,9 +70,9 @@
         return "HdrBrightnessData {"
                 + "mMaxBrightnessLimits: " + mMaxBrightnessLimits
                 + ", mBrightnessIncreaseDebounceMillis: " + mBrightnessIncreaseDebounceMillis
-                + ", mBrightnessIncreaseDurationMillis: " + mBrightnessIncreaseDurationMillis
+                + ", mScreenBrightnessRampIncrease: " + mScreenBrightnessRampIncrease
                 + ", mBrightnessDecreaseDebounceMillis: " + mBrightnessDecreaseDebounceMillis
-                + ", mBrightnessDecreaseDurationMillis: " + mBrightnessDecreaseDurationMillis
+                + ", mScreenBrightnessRampDecrease: " + mScreenBrightnessRampDecrease
                 + "} ";
     }
 
@@ -94,8 +94,8 @@
 
         return new HdrBrightnessData(brightnessLimits,
                 hdrConfig.getBrightnessIncreaseDebounceMillis().longValue(),
-                hdrConfig.getBrightnessIncreaseDurationMillis().longValue(),
+                hdrConfig.getScreenBrightnessRampIncrease().floatValue(),
                 hdrConfig.getBrightnessDecreaseDebounceMillis().longValue(),
-                hdrConfig.getBrightnessDecreaseDurationMillis().longValue());
+                hdrConfig.getScreenBrightnessRampDecrease().floatValue());
     }
 }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index f168f43..f35b045 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -240,6 +240,10 @@
     private static final String LSKF_LAST_CHANGED_TIME_KEY = "sp-handle-ts";
     private static final String USER_SERIAL_NUMBER_KEY = "serial-number";
 
+    private static final String MIGRATED_FRP2 = "migrated_frp2";
+    private static final String MIGRATED_KEYSTORE_NS = "migrated_keystore_namespace";
+    private static final String MIGRATED_SP_CE_ONLY = "migrated_all_users_to_sp_and_bound_ce";
+
     // Duration that LockSettingsService will store the gatekeeper password for. This allows
     // multiple biometric enrollments without prompting the user to enter their password via
     // ConfirmLockPassword/ConfirmLockPattern multiple times. This needs to be at least the duration
@@ -909,14 +913,14 @@
     }
 
     private void migrateOldData() {
-        if (getString("migrated_keystore_namespace", null, 0) == null) {
+        if (getString(MIGRATED_KEYSTORE_NS, null, 0) == null) {
             boolean success = true;
             synchronized (mSpManager) {
                 success &= mSpManager.migrateKeyNamespace();
             }
             success &= migrateProfileLockKeys();
             if (success) {
-                setString("migrated_keystore_namespace", "true", 0);
+                setString(MIGRATED_KEYSTORE_NS, "true", 0);
                 Slog.i(TAG, "Migrated keys to LSS namespace");
             } else {
                 Slog.w(TAG, "Failed to migrate keys to LSS namespace");
@@ -936,9 +940,9 @@
         // "migrated_frp" to "migrated_frp2" to cause migrateFrpCredential() to run again on devices
         // where it had run before.
         if (LockPatternUtils.frpCredentialEnabled(mContext)
-                && !getBoolean("migrated_frp2", false, 0)) {
+                && !getBoolean(MIGRATED_FRP2, false, 0)) {
             migrateFrpCredential();
-            setBoolean("migrated_frp2", true, 0);
+            setBoolean(MIGRATED_FRP2, true, 0);
         }
     }
 
@@ -1028,14 +1032,14 @@
             // If this gets interrupted (e.g. by the device powering off), there shouldn't be a
             // problem since this will run again on the next boot, and setUserKeyProtection() is
             // okay with the key being already protected by the given secret.
-            if (getString("migrated_all_users_to_sp_and_bound_ce", null, 0) == null) {
+            if (getString(MIGRATED_SP_CE_ONLY, null, 0) == null) {
                 for (UserInfo user : mUserManager.getAliveUsers()) {
                     removeStateForReusedUserIdIfNecessary(user.id, user.serialNumber);
                     synchronized (mSpManager) {
                         migrateUserToSpWithBoundCeKeyLocked(user.id);
                     }
                 }
-                setString("migrated_all_users_to_sp_and_bound_ce", "true", 0);
+                setString(MIGRATED_SP_CE_ONLY, "true", 0);
             }
 
             mThirdPartyAppsStarted = true;
@@ -1062,7 +1066,7 @@
                 Slogf.wtf(TAG, "Failed to unwrap synthetic password for unsecured user %d", userId);
                 return;
             }
-            setUserKeyProtection(userId, result.syntheticPassword.deriveFileBasedEncryptionKey());
+            setUserKeyProtection(userId, result.syntheticPassword);
         }
     }
 
@@ -1347,8 +1351,8 @@
         AndroidKeyStoreMaintenance.onUserPasswordChanged(userHandle, password);
     }
 
-    private void unlockKeystore(byte[] password, int userHandle) {
-        Authorization.onLockScreenEvent(false, userHandle, password, null);
+    private void unlockKeystore(int userId, SyntheticPassword sp) {
+        Authorization.onLockScreenEvent(false, userId, sp.deriveKeyStorePassword(), null);
     }
 
     @VisibleForTesting /** Note: this method is overridden in unit tests */
@@ -2001,7 +2005,8 @@
         mStorage.writeChildProfileLock(profileUserId, ArrayUtils.concat(iv, ciphertext));
     }
 
-    private void setUserKeyProtection(@UserIdInt int userId, byte[] secret) {
+    private void setUserKeyProtection(@UserIdInt int userId, SyntheticPassword sp) {
+        final byte[] secret = sp.deriveFileBasedEncryptionKey();
         final long callingId = Binder.clearCallingIdentity();
         try {
             mStorageManager.setUserKeyProtection(userId, secret);
@@ -2045,7 +2050,9 @@
         }
     }
 
-    private void unlockUserKeyIfUnsecured(@UserIdInt int userId) {
+    @Override
+    public void unlockUserKeyIfUnsecured(@UserIdInt int userId) {
+        checkPasswordReadPermission();
         synchronized (mSpManager) {
             if (isUserKeyUnlocked(userId)) {
                 Slogf.d(TAG, "CE storage for user %d is already unlocked", userId);
@@ -2768,7 +2775,7 @@
             final long protectorId = mSpManager.createLskfBasedProtector(getGateKeeperService(),
                     LockscreenCredential.createNone(), sp, userId);
             setCurrentLskfBasedProtectorId(protectorId, userId);
-            setUserKeyProtection(userId, sp.deriveFileBasedEncryptionKey());
+            setUserKeyProtection(userId, sp);
             onSyntheticPasswordCreated(userId, sp);
             Slogf.i(TAG, "Successfully initialized synthetic password for user %d", userId);
             return sp;
@@ -2827,7 +2834,7 @@
             }
         }
 
-        unlockKeystore(sp.deriveKeyStorePassword(), userId);
+        unlockKeystore(userId, sp);
 
         unlockUserKey(userId, sp);
 
@@ -2894,7 +2901,7 @@
             mSpManager.clearSidForUser(userId);
             gateKeeperClearSecureUserId(userId);
             unlockUserKey(userId, sp);
-            unlockKeystore(sp.deriveKeyStorePassword(), userId);
+            unlockKeystore(userId, sp);
             setKeystorePassword(null, userId);
             removeBiometricsForUser(userId);
         }
@@ -3454,11 +3461,6 @@
         }
 
         @Override
-        public void unlockUserKeyIfUnsecured(@UserIdInt int userId) {
-            LockSettingsService.this.unlockUserKeyIfUnsecured(userId);
-        }
-
-        @Override
         public void createNewUser(@UserIdInt int userId, int userSerialNumber) {
             LockSettingsService.this.createNewUser(userId, userSerialNumber);
         }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
index df95c69..4bac872 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
@@ -174,7 +174,7 @@
             pw.println("    Sets the lock screen as PIN, using the given PIN to unlock.");
             pw.println("");
             pw.println("  set-password [--old <CREDENTIAL>] [--user USER_ID] <PASSWORD>");
-            pw.println("    Sets the lock screen as password, using the given PASSOWRD to unlock.");
+            pw.println("    Sets the lock screen as password, using the given PASSWORD to unlock.");
             pw.println("");
             pw.println("  clear [--old <CREDENTIAL>] [--user USER_ID]");
             pw.println("    Clears the lock credentials.");
diff --git a/services/core/java/com/android/server/media/AudioAttributesUtils.java b/services/core/java/com/android/server/media/AudioAttributesUtils.java
index 5d5d59b..8cb334d 100644
--- a/services/core/java/com/android/server/media/AudioAttributesUtils.java
+++ b/services/core/java/com/android/server/media/AudioAttributesUtils.java
@@ -23,6 +23,8 @@
 import android.media.AudioDeviceInfo;
 import android.media.MediaRoute2Info;
 
+import com.android.media.flags.Flags;
+
 /* package */ final class AudioAttributesUtils {
 
     /* package */ static final AudioAttributes ATTRIBUTES_MEDIA = new AudioAttributes.Builder()
@@ -36,6 +38,14 @@
     @MediaRoute2Info.Type
     /* package */ static int mapToMediaRouteType(
             @NonNull AudioDeviceAttributes audioDeviceAttributes) {
+        if (Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
+            switch (audioDeviceAttributes.getType()) {
+                case AudioDeviceInfo.TYPE_HDMI_ARC:
+                    return MediaRoute2Info.TYPE_HDMI_ARC;
+                case AudioDeviceInfo.TYPE_HDMI_EARC:
+                    return MediaRoute2Info.TYPE_HDMI_EARC;
+            }
+        }
         switch (audioDeviceAttributes.getType()) {
             case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE:
             case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
@@ -64,7 +74,6 @@
         }
     }
 
-
     /* package */ static boolean isDeviceOutputAttributes(
             @Nullable AudioDeviceAttributes audioDeviceAttributes) {
         if (audioDeviceAttributes == null) {
diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
index 33190ad..360a6a7 100644
--- a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
@@ -22,6 +22,8 @@
 import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
 import static android.media.MediaRoute2Info.TYPE_DOCK;
 import static android.media.MediaRoute2Info.TYPE_HDMI;
+import static android.media.MediaRoute2Info.TYPE_HDMI_ARC;
+import static android.media.MediaRoute2Info.TYPE_HDMI_EARC;
 import static android.media.MediaRoute2Info.TYPE_USB_DEVICE;
 import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
 import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
@@ -160,7 +162,6 @@
     @NonNull
     private MediaRoute2Info createRouteFromAudioInfo(@MediaRoute2Info.Type int type) {
         int name = R.string.default_audio_route_name;
-
         switch (type) {
             case TYPE_WIRED_HEADPHONES:
             case TYPE_WIRED_HEADSET:
@@ -170,6 +171,8 @@
                 name = R.string.default_audio_route_name_dock_speakers;
                 break;
             case TYPE_HDMI:
+            case TYPE_HDMI_ARC:
+            case TYPE_HDMI_EARC:
                 name = R.string.default_audio_route_name_external_device;
                 break;
             case TYPE_USB_DEVICE:
@@ -211,6 +214,8 @@
             case TYPE_WIRED_HEADSET:
             case TYPE_DOCK:
             case TYPE_HDMI:
+            case TYPE_HDMI_ARC:
+            case TYPE_HDMI_EARC:
             case TYPE_USB_DEVICE:
                 return true;
             default:
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index cc261a4..44719f8 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -515,8 +515,7 @@
 
     // Binder call
     @Override
-    public RoutingSessionInfo getSystemSessionInfoForPackage(IMediaRouter2Manager manager,
-            String packageName) {
+    public RoutingSessionInfo getSystemSessionInfoForPackage(@Nullable String packageName) {
         final int uid = Binder.getCallingUid();
         final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
         boolean setDeviceRouteSelected = false;
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 0e8f907..2c59511 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -268,7 +268,14 @@
             }
             if (record.isSystemPriority()) {
                 if (DEBUG_KEY_EVENT) {
-                    Log.d(TAG, "Global priority session is updated, active=" + record.isActive());
+                    Log.d(
+                            TAG,
+                            "Global priority session updated - user id="
+                                    + record.getUserId()
+                                    + " package="
+                                    + record.getPackageName()
+                                    + " active="
+                                    + record.isActive());
                 }
                 user.pushAddressedPlayerChangedLocked();
             } else {
diff --git a/services/core/java/com/android/server/media/projection/FrameworkStatsLogWrapper.java b/services/core/java/com/android/server/media/projection/FrameworkStatsLogWrapper.java
new file mode 100644
index 0000000..5bad067
--- /dev/null
+++ b/services/core/java/com/android/server/media/projection/FrameworkStatsLogWrapper.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media.projection;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+/** Wrapper around {@link FrameworkStatsLog} */
+public class FrameworkStatsLogWrapper {
+
+    /** Wrapper around {@link FrameworkStatsLog#write}. */
+    public void write(
+            int code,
+            int sessionId,
+            int state,
+            int previousState,
+            int hostUid,
+            int targetUid,
+            int timeSinceLastActive,
+            int creationSource) {
+        FrameworkStatsLog.write(
+                code,
+                sessionId,
+                state,
+                previousState,
+                hostUid,
+                targetUid,
+                timeSinceLastActive,
+                creationSource);
+    }
+}
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 8cbc368..ce35a61 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -162,7 +162,7 @@
         mWmInternal = LocalServices.getService(WindowManagerInternal.class);
         mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE);
         mMediaRouterCallback = new MediaRouterCallback();
-        mMediaProjectionMetricsLogger = injector.mediaProjectionMetricsLogger();
+        mMediaProjectionMetricsLogger = injector.mediaProjectionMetricsLogger(context);
         Watchdog.getInstance().addMonitor(this);
     }
 
@@ -197,8 +197,8 @@
             return Looper.getMainLooper();
         }
 
-        MediaProjectionMetricsLogger mediaProjectionMetricsLogger() {
-            return MediaProjectionMetricsLogger.getInstance();
+        MediaProjectionMetricsLogger mediaProjectionMetricsLogger(Context context) {
+            return MediaProjectionMetricsLogger.getInstance(context);
         }
     }
 
@@ -293,6 +293,12 @@
     private void stopProjectionLocked(final MediaProjection projection) {
         Slog.d(TAG, "Content Recording: Stopped active MediaProjection and "
                 + "dispatching stop to callbacks");
+        ContentRecordingSession session = projection.mSession;
+        int targetUid =
+                session != null
+                        ? session.getTargetUid()
+                        : ContentRecordingSession.TARGET_UID_UNKNOWN;
+        mMediaProjectionMetricsLogger.logStopped(projection.uid, targetUid);
         mProjectionToken = null;
         mProjectionGrant = null;
         dispatchStop(projection);
@@ -452,6 +458,11 @@
                 .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
     }
 
+    @VisibleForTesting
+    void notifyPermissionRequestInitiated(int hostUid, int sessionCreationSource) {
+        mMediaProjectionMetricsLogger.logInitiated(hostUid, sessionCreationSource);
+    }
+
     /**
      * Handles result of dialog shown from
      * {@link BinderService#buildReviewGrantedConsentIntentLocked()}.
@@ -842,6 +853,19 @@
         }
 
         @Override // Binder call
+        @EnforcePermission(MANAGE_MEDIA_PROJECTION)
+        public void notifyPermissionRequestInitiated(int hostUid, int sessionCreationSource) {
+            notifyPermissionRequestInitiated_enforcePermission();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                MediaProjectionManagerService.this.notifyPermissionRequestInitiated(
+                        hostUid, sessionCreationSource);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override // Binder call
         public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
             final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java
index f18ecad..c8b932a 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java
@@ -16,35 +16,127 @@
 
 package com.android.server.media.projection;
 
+import android.content.Context;
 
 import com.android.internal.util.FrameworkStatsLog;
 
-/**
- * Class for emitting logs describing a MediaProjection session.
- */
+import java.time.Duration;
+
+/** Class for emitting logs describing a MediaProjection session. */
 public class MediaProjectionMetricsLogger {
+    private static final int TARGET_UID_UNKNOWN = -2;
+    private static final int TIME_SINCE_LAST_ACTIVE_UNKNOWN = -1;
+
     private static MediaProjectionMetricsLogger sSingleton = null;
 
-    public static MediaProjectionMetricsLogger getInstance() {
+    private final FrameworkStatsLogWrapper mFrameworkStatsLogWrapper;
+    private final MediaProjectionSessionIdGenerator mSessionIdGenerator;
+    private final MediaProjectionTimestampStore mTimestampStore;
+
+    private int mPreviousState =
+            FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN;
+
+    MediaProjectionMetricsLogger(
+            FrameworkStatsLogWrapper frameworkStatsLogWrapper,
+            MediaProjectionSessionIdGenerator sessionIdGenerator,
+            MediaProjectionTimestampStore timestampStore) {
+        mFrameworkStatsLogWrapper = frameworkStatsLogWrapper;
+        mSessionIdGenerator = sessionIdGenerator;
+        mTimestampStore = timestampStore;
+    }
+
+    /** Returns a singleton instance of {@link MediaProjectionMetricsLogger}. */
+    public static MediaProjectionMetricsLogger getInstance(Context context) {
         if (sSingleton == null) {
-            sSingleton = new MediaProjectionMetricsLogger();
+            sSingleton =
+                    new MediaProjectionMetricsLogger(
+                            new FrameworkStatsLogWrapper(),
+                            MediaProjectionSessionIdGenerator.getInstance(context),
+                            MediaProjectionTimestampStore.getInstance(context));
         }
         return sSingleton;
     }
 
-    void notifyProjectionStateChange(int hostUid, int state, int sessionCreationSource) {
+    /**
+     * Logs that the media projection session was initiated by the app requesting the user's consent
+     * to capture. Should be sent even if the permission dialog is not shown.
+     *
+     * @param hostUid UID of the package that initiates MediaProjection.
+     * @param sessionCreationSource Where this session started. One of:
+     *     <ul>
+     *       <li>{@link
+     *           FrameworkStatsLog#MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_APP}
+     *       <li>{@link
+     *           FrameworkStatsLog#MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_CAST}
+     *       <li>{@link
+     *           FrameworkStatsLog#MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER}
+     *       <li>{@link
+     *           FrameworkStatsLog#MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN}
+     *     </ul>
+     */
+    public void logInitiated(int hostUid, int sessionCreationSource) {
+        Duration durationSinceLastActiveSession = mTimestampStore.timeSinceLastActiveSession();
+        int timeSinceLastActiveInSeconds =
+                durationSinceLastActiveSession == null
+                        ? TIME_SINCE_LAST_ACTIVE_UNKNOWN
+                        : (int) durationSinceLastActiveSession.toSeconds();
+        write(
+                mSessionIdGenerator.createAndGetNewSessionId(),
+                FrameworkStatsLog
+                        .MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED,
+                hostUid,
+                TARGET_UID_UNKNOWN,
+                timeSinceLastActiveInSeconds,
+                sessionCreationSource);
+    }
+
+    /** Logs that the capturing stopped, either normally or because of error. */
+    public void logStopped(int hostUid, int targetUid) {
+        write(
+                mSessionIdGenerator.getCurrentSessionId(),
+                FrameworkStatsLog
+                        .MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED,
+                hostUid,
+                targetUid,
+                TIME_SINCE_LAST_ACTIVE_UNKNOWN,
+                FrameworkStatsLog
+                        .MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+        mTimestampStore.registerActiveSessionEnded();
+    }
+
+    public void notifyProjectionStateChange(int hostUid, int state, int sessionCreationSource) {
         write(hostUid, state, sessionCreationSource);
     }
 
     private void write(int hostUid, int state, int sessionCreationSource) {
-        FrameworkStatsLog.write(
+        mFrameworkStatsLogWrapper.write(
                 /* code */ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED,
                 /* session_id */ 123,
                 /* state */ state,
-                /* previous_state */ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN,
+                /* previous_state */ FrameworkStatsLog
+                        .MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN,
                 /* host_uid */ hostUid,
                 /* target_uid */ -1,
                 /* time_since_last_active */ 0,
                 /* creation_source */ sessionCreationSource);
     }
+
+    private void write(
+            int sessionId,
+            int state,
+            int hostUid,
+            int targetUid,
+            int timeSinceLastActive,
+            int creationSource) {
+        mFrameworkStatsLogWrapper.write(
+                /* code */ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED,
+                sessionId,
+                state,
+                mPreviousState,
+                hostUid,
+                targetUid,
+                timeSinceLastActive,
+                creationSource);
+        mPreviousState = state;
+    }
 }
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java b/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java
index ff70cb3..244de0b 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java
@@ -47,8 +47,11 @@
             if (sInstance == null) {
                 File preferencesFile =
                         new File(Environment.getDataSystemDirectory(), PREFERENCES_FILE_NAME);
+                // Needed as this class is instantiated before the device is unlocked.
+                Context directBootContext = context.createDeviceProtectedStorageContext();
                 SharedPreferences preferences =
-                        context.getSharedPreferences(preferencesFile, Context.MODE_PRIVATE);
+                        directBootContext.getSharedPreferences(
+                                preferencesFile, Context.MODE_PRIVATE);
                 sInstance = new MediaProjectionSessionIdGenerator(preferences);
             }
             return sInstance;
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionTimestampStore.java b/services/core/java/com/android/server/media/projection/MediaProjectionTimestampStore.java
new file mode 100644
index 0000000..bfec58c
--- /dev/null
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionTimestampStore.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media.projection;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Environment;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.InstantSource;
+
+/** Stores timestamps of media projection sessions. */
+public class MediaProjectionTimestampStore {
+    private static final String PREFERENCES_FILE_NAME = "media_projection_timestamp";
+    private static final String TIMESTAMP_PREF_KEY = "media_projection_timestamp_key";
+    private static final Object sInstanceLock = new Object();
+
+    @GuardedBy("sInstanceLock")
+    private static MediaProjectionTimestampStore sInstance;
+
+    private final Object mTimestampLock = new Object();
+
+    @GuardedBy("mTimestampLock")
+    private final SharedPreferences mSharedPreferences;
+
+    private final InstantSource mInstantSource;
+
+    @VisibleForTesting
+    public MediaProjectionTimestampStore(
+            SharedPreferences sharedPreferences, InstantSource instantSource) {
+        this.mSharedPreferences = sharedPreferences;
+        this.mInstantSource = instantSource;
+    }
+
+    /** Creates or returns an existing instance of {@link MediaProjectionTimestampStore}. */
+    public static MediaProjectionTimestampStore getInstance(Context context) {
+        synchronized (sInstanceLock) {
+            if (sInstance == null) {
+                File preferencesFile =
+                        new File(Environment.getDataSystemDirectory(), PREFERENCES_FILE_NAME);
+                // Needed as this class is instantiated before the device is unlocked.
+                Context directBootContext = context.createDeviceProtectedStorageContext();
+                SharedPreferences preferences =
+                        directBootContext.getSharedPreferences(
+                                preferencesFile, Context.MODE_PRIVATE);
+                sInstance = new MediaProjectionTimestampStore(preferences, InstantSource.system());
+            }
+            return sInstance;
+        }
+    }
+
+    /**
+     * Returns the time that has passed since the last active session, or {@code null} if there was
+     * no last active session.
+     */
+    @Nullable
+    public Duration timeSinceLastActiveSession() {
+        synchronized (mTimestampLock) {
+            Instant lastActiveSessionTimestamp = getLastActiveSessionTimestamp();
+            if (lastActiveSessionTimestamp == null) {
+                return null;
+            }
+            Instant now = mInstantSource.instant();
+            return Duration.between(lastActiveSessionTimestamp, now);
+        }
+    }
+
+    /** Registers that the current active session ended now. */
+    public void registerActiveSessionEnded() {
+        synchronized (mTimestampLock) {
+            Instant now = mInstantSource.instant();
+            setLastActiveSessionTimestamp(now);
+        }
+    }
+
+    @GuardedBy("mTimestampLock")
+    @Nullable
+    private Instant getLastActiveSessionTimestamp() {
+        long lastActiveSessionEpochMilli =
+                mSharedPreferences.getLong(TIMESTAMP_PREF_KEY, /* defValue= */ -1);
+        if (lastActiveSessionEpochMilli == -1) {
+            return null;
+        }
+        return Instant.ofEpochMilli(lastActiveSessionEpochMilli);
+    }
+
+    @GuardedBy("mTimestampLock")
+    private void setLastActiveSessionTimestamp(@NonNull Instant timestamp) {
+        mSharedPreferences.edit().putLong(TIMESTAMP_PREF_KEY, timestamp.toEpochMilli()).apply();
+    }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 837b761..7ca5699 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -71,6 +71,7 @@
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
+import static android.os.Flags.allowPrivateProfile;
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
 import static android.os.PowerWhitelistManager.REASON_NOTIFICATION_SERVICE;
@@ -289,7 +290,6 @@
 import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags;
 import com.android.internal.logging.InstanceId;
 import com.android.internal.logging.InstanceIdSequence;
 import com.android.internal.logging.MetricsLogger;
@@ -1179,7 +1179,7 @@
         @Override
         public void onSetDisabled(int status) {
             synchronized (mNotificationLock) {
-                if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+                if (Flags.refactorAttentionHelper()) {
                     mAttentionHelper.updateDisableNotificationEffectsLocked(status);
                 } else {
                     mDisableNotificationEffects =
@@ -1325,7 +1325,7 @@
         public void clearEffects() {
             synchronized (mNotificationLock) {
                 if (DBG) Slog.d(TAG, "clearEffects");
-                if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+                if (Flags.refactorAttentionHelper()) {
                     mAttentionHelper.clearAttentionEffects();
                 } else {
                     clearSoundLocked();
@@ -1554,8 +1554,7 @@
                         int changedFlags = data.getFlags() ^ flags;
                         if ((changedFlags & FLAG_SUPPRESS_NOTIFICATION) != 0) {
                             // Suppress notification flag changed, clear any effects
-                            if (mFlagResolver.isEnabled(
-                                    NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+                            if (Flags.refactorAttentionHelper()) {
                                 mAttentionHelper.clearEffectsLocked(key);
                             } else {
                                 clearEffectsLocked(key);
@@ -1904,7 +1903,7 @@
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
 
-            if (!mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+            if (!Flags.refactorAttentionHelper()) {
                 if (action.equals(Intent.ACTION_SCREEN_ON)) {
                     // Keep track of screen on/off state, but do not turn off the notification light
                     // until user passes through the lock screen or views the notification.
@@ -1931,7 +1930,8 @@
                     cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, userHandle,
                             REASON_USER_STOPPED);
                 }
-            } else if (action.equals(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)) {
+            } else if (
+                    isProfileUnavailable(action)) {
                 int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
                 if (userHandle >= 0 && !mDpm.isKeepProfilesRunningEnabled()) {
                     cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, userHandle,
@@ -1982,6 +1982,12 @@
                 }
             }
         }
+
+        private boolean isProfileUnavailable(String action) {
+            return allowPrivateProfile() ?
+                    action.equals(Intent.ACTION_PROFILE_UNAVAILABLE) :
+                    action.equals(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
+        }
     };
 
     private final class SettingsObserver extends ContentObserver {
@@ -2011,7 +2017,7 @@
             ContentResolver resolver = getContext().getContentResolver();
             resolver.registerContentObserver(NOTIFICATION_BADGING_URI,
                     false, this, UserHandle.USER_ALL);
-            if (!mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+            if (!Flags.refactorAttentionHelper()) {
                 resolver.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI,
                     false, this, UserHandle.USER_ALL);
             }
@@ -2037,7 +2043,7 @@
 
         public void update(Uri uri) {
             ContentResolver resolver = getContext().getContentResolver();
-            if (!mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+            if (!Flags.refactorAttentionHelper()) {
                 if (uri == null || NOTIFICATION_LIGHT_PULSE_URI.equals(uri)) {
                     boolean pulseEnabled = Settings.System.getIntForUser(resolver,
                         Settings.System.NOTIFICATION_LIGHT_PULSE, 0, UserHandle.USER_CURRENT)
@@ -2530,7 +2536,7 @@
 
         mToastRateLimiter = toastRateLimiter;
 
-        if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+        if (Flags.refactorAttentionHelper()) {
             mAttentionHelper = new NotificationAttentionHelper(getContext(), lightsManager,
                 mAccessibilityManager, mPackageManagerClient, userManager, usageStats,
                 mNotificationManagerPrivate, mZenModeHelper, flagResolver);
@@ -2540,7 +2546,7 @@
         // If this is called within a test, make sure to unregister the intent receivers by
         // calling onDestroy()
         IntentFilter filter = new IntentFilter();
-        if (!mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+        if (!Flags.refactorAttentionHelper()) {
             filter.addAction(Intent.ACTION_SCREEN_ON);
             filter.addAction(Intent.ACTION_SCREEN_OFF);
             filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
@@ -2552,6 +2558,9 @@
         filter.addAction(Intent.ACTION_USER_REMOVED);
         filter.addAction(Intent.ACTION_USER_UNLOCKED);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
+        if (allowPrivateProfile()){
+            filter.addAction(Intent.ACTION_PROFILE_UNAVAILABLE);
+        }
         getContext().registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter, null, null);
 
         IntentFilter pkgFilter = new IntentFilter();
@@ -2865,7 +2874,7 @@
             }
             registerNotificationPreferencesPullers();
             new LockPatternUtils(getContext()).registerStrongAuthTracker(mStrongAuthTracker);
-            if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+            if (Flags.refactorAttentionHelper()) {
                 mAttentionHelper.onSystemReady();
             }
         } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
@@ -3500,8 +3509,19 @@
                                 null /* options */);
                         record = getToastRecord(callingUid, callingPid, pkg, isSystemToast, token,
                                 text, callback, duration, windowToken, displayId, textCallback);
-                        mToastQueue.add(record);
-                        index = mToastQueue.size() - 1;
+
+                        // Insert system toasts at the front of the queue
+                        int systemToastInsertIdx = mToastQueue.size();
+                        if (isSystemToast) {
+                            systemToastInsertIdx = getInsertIndexForSystemToastLocked();
+                        }
+                        if (systemToastInsertIdx < mToastQueue.size()) {
+                            index = systemToastInsertIdx;
+                            mToastQueue.add(index, record);
+                        } else {
+                            mToastQueue.add(record);
+                            index = mToastQueue.size() - 1;
+                        }
                         keepProcessAliveForToastIfNeededLocked(callingPid);
                     }
                     // If it's at index 0, it's the current toast.  It doesn't matter if it's
@@ -3517,6 +3537,23 @@
             }
         }
 
+        @GuardedBy("mToastQueue")
+        private int getInsertIndexForSystemToastLocked() {
+            // If there are other system toasts: insert after the last one
+            int idx = 0;
+            for (ToastRecord r : mToastQueue) {
+                if (idx == 0 && mIsCurrentToastShown) {
+                    idx++;
+                    continue;
+                }
+                if (!r.isSystemToast) {
+                    return idx;
+                }
+                idx++;
+            }
+            return idx;
+        }
+
         private boolean checkCanEnqueueToast(String pkg, int callingUid, int displayId,
                 boolean isAppRenderedToast, boolean isSystemToast) {
             final boolean isPackageSuspended = isPackagePaused(pkg);
@@ -6462,7 +6499,7 @@
                     pw.println("  mMaxPackageEnqueueRate=" + mMaxPackageEnqueueRate);
                     pw.println("  hideSilentStatusBar="
                             + mPreferencesHelper.shouldHideSilentStatusIcons());
-                    if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+                    if (Flags.refactorAttentionHelper()) {
                         mAttentionHelper.dump(pw, "    ", filter);
                     }
                 }
@@ -7728,7 +7765,7 @@
             boolean wasPosted = removeFromNotificationListsLocked(r);
             cancelNotificationLocked(r, false, REASON_SNOOZED, wasPosted, null,
                     SystemClock.elapsedRealtime());
-            if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+            if (Flags.refactorAttentionHelper()) {
                 mAttentionHelper.updateLightsLocked();
             } else {
                 updateLightsLocked();
@@ -7861,7 +7898,7 @@
                     cancelGroupChildrenLocked(r, mCallingUid, mCallingPid, listenerName,
                             mSendDelete, childrenFlagChecker, mReason,
                             mCancellationElapsedTimeMs);
-                    if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+                    if (Flags.refactorAttentionHelper()) {
                         mAttentionHelper.updateLightsLocked();
                     } else {
                         updateLightsLocked();
@@ -8158,7 +8195,7 @@
 
                     int buzzBeepBlinkLoggingCode = 0;
                     if (!r.isHidden()) {
-                        if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+                        if (Flags.refactorAttentionHelper()) {
                             buzzBeepBlinkLoggingCode = mAttentionHelper.buzzBeepBlinkLocked(r,
                                 new NotificationAttentionHelper.Signals(
                                     mUserProfiles.isCurrentProfile(r.getUserId()),
@@ -9145,7 +9182,7 @@
                     || interruptiveChanged;
             if (interceptBefore && !record.isIntercepted()
                     && record.isNewEnoughForAlerting(System.currentTimeMillis())) {
-                if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+                if (Flags.refactorAttentionHelper()) {
                     mAttentionHelper.buzzBeepBlinkLocked(record,
                         new NotificationAttentionHelper.Signals(
                             mUserProfiles.isCurrentProfile(record.getUserId()), mListenerHints));
@@ -9525,7 +9562,7 @@
                 });
             }
 
-            if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+            if (Flags.refactorAttentionHelper()) {
                 mAttentionHelper.clearEffectsLocked(canceledKey);
             } else {
                 // sound
@@ -9889,7 +9926,7 @@
                             cancellationElapsedTimeMs);
                 }
             }
-            if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+            if (Flags.refactorAttentionHelper()) {
                 mAttentionHelper.updateLightsLocked();
             } else {
                 updateLightsLocked();
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index f59188e..0fb1f7a 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -196,8 +196,12 @@
             for (int i = 0, size = mainActivities.length; i < size; ++i) {
                 var mainActivity = mainActivities[i];
                 Path iconPath = storeIconForParcel(packageName, mainActivity, userId, i);
-                ArchiveActivityInfo activityInfo = new ArchiveActivityInfo(
-                        mainActivity.title, iconPath, null);
+                ArchiveActivityInfo activityInfo =
+                        new ArchiveActivityInfo(
+                                mainActivity.title,
+                                mainActivity.originalComponentName,
+                                iconPath,
+                                null);
                 archiveActivityInfos.add(activityInfo);
             }
 
@@ -215,8 +219,12 @@
         for (int i = 0, size = mainActivities.size(); i < size; i++) {
             LauncherActivityInfo mainActivity = mainActivities.get(i);
             Path iconPath = storeIcon(packageName, mainActivity, userId, i);
-            ArchiveActivityInfo activityInfo = new ArchiveActivityInfo(
-                    mainActivity.getLabel().toString(), iconPath, null);
+            ArchiveActivityInfo activityInfo =
+                    new ArchiveActivityInfo(
+                            mainActivity.getLabel().toString(),
+                            mainActivity.getComponentName(),
+                            iconPath,
+                            null);
             archiveActivityInfos.add(activityInfo);
         }
 
@@ -593,6 +601,7 @@
             }
             var archivedActivity = new ArchivedActivityParcel();
             archivedActivity.title = info.getTitle();
+            archivedActivity.originalComponentName = info.getOriginalComponentName();
             archivedActivity.iconBitmap = bytesFromBitmapFile(info.getIconBitmap());
             archivedActivity.monochromeIconBitmap = bytesFromBitmapFile(
                     info.getMonochromeIconBitmap());
@@ -624,6 +633,7 @@
             }
             var archivedActivity = new ArchivedActivityParcel();
             archivedActivity.title = info.getLabel().toString();
+            archivedActivity.originalComponentName = info.getComponentName();
             archivedActivity.iconBitmap =
                     info.getActivityInfo().getIconResource() == 0 ? null : bytesFromBitmap(
                             drawableToBitmap(info.getIcon(/* density= */ 0)));
diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
index 651845e..e749968 100644
--- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
+++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@@ -747,7 +747,7 @@
 
     @Override
     public void notifyComponentUsed(@NonNull String packageName, @UserIdInt int userId,
-            @NonNull String recentCallingPackage, @NonNull String debugInfo) {
+            @Nullable String recentCallingPackage, @NonNull String debugInfo) {
         mService.notifyComponentUsed(snapshot(), packageName, userId,
                 recentCallingPackage, debugInfo);
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index abeabc9..61b6b24 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -36,6 +36,7 @@
 import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
 import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
 import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE;
+import static android.util.FeatureFlagUtils.SETTINGS_TREAT_PAUSE_AS_QUARANTINE;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility;
 import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_INIT_TIME;
@@ -164,6 +165,7 @@
 import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.ExceptionUtils;
+import android.util.FeatureFlagUtils;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
@@ -4564,6 +4566,7 @@
                 final Bundle extras = new Bundle();
                 extras.putInt(Intent.EXTRA_UID, pmi.getPackageUid(packageName, 0, userId));
                 extras.putInt(Intent.EXTRA_USER_HANDLE, userId);
+                extras.putLong(Intent.EXTRA_TIME, SystemClock.elapsedRealtime());
                 mHandler.post(() -> {
                     mBroadcastHelper.sendPackageBroadcast(Intent.ACTION_PACKAGE_UNSTOPPED,
                             packageName, extras,
@@ -4576,7 +4579,7 @@
     }
 
     void notifyComponentUsed(@NonNull Computer snapshot, @NonNull String packageName,
-            @UserIdInt int userId, @NonNull String recentCallingPackage,
+            @UserIdInt int userId, @Nullable String recentCallingPackage,
             @NonNull String debugInfo) {
         synchronized (mLock) {
             final PackageSetting pkgSetting = mSettings.getPackageLPr(packageName);
@@ -6133,8 +6136,16 @@
             final Computer snapshot = snapshotComputer();
             enforceCanSetPackagesSuspendedAsUser(snapshot, callingPackage, callingUid, userId,
                     "setPackagesSuspendedAsUser");
-            boolean quarantined = ((flags & PackageManager.FLAG_SUSPEND_QUARANTINED) != 0)
-                    && Flags.quarantinedEnabled();
+            boolean quarantined = false;
+            if (Flags.quarantinedEnabled()) {
+                if ((flags & PackageManager.FLAG_SUSPEND_QUARANTINED) != 0) {
+                    quarantined = true;
+                } else if (FeatureFlagUtils.isEnabled(mContext,
+                        SETTINGS_TREAT_PAUSE_AS_QUARANTINE)) {
+                    final String wellbeingPkg = mContext.getString(R.string.config_systemWellbeing);
+                    quarantined = callingPackage.equals(wellbeingPkg);
+                }
+            }
             return mSuspendPackageHelper.setPackagesSuspended(snapshot, packageNames, suspended,
                     appExtras, launcherExtras, dialogInfo, callingPackage, userId, callingUid,
                     false /* forQuietMode */, quarantined);
@@ -6959,6 +6970,7 @@
             final Bundle extras = new Bundle();
             extras.putInt(Intent.EXTRA_UID, uid);
             extras.putInt(Intent.EXTRA_USER_HANDLE, userId);
+            extras.putLong(Intent.EXTRA_TIME, SystemClock.elapsedRealtime());
             mHandler.post(() -> {
                 mBroadcastHelper.sendPackageBroadcast(Intent.ACTION_PACKAGE_RESTARTED,
                         packageName, extras,
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 9f4e86d..42f4cfb 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -238,34 +238,16 @@
     }
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    public PackageSetting(String name, String realName, @NonNull File path,
-            String legacyNativeLibraryPath, String primaryCpuAbi,
-            String secondaryCpuAbi, String cpuAbiOverride,
-            long longVersionCode, int pkgFlags, int pkgPrivateFlags,
-            int sharedUserAppId,
-            String[] usesSdkLibraries, long[] usesSdkLibrariesVersionsMajor,
-            String[] usesStaticLibraries, long[] usesStaticLibrariesVersions,
-            Map<String, Set<String>> mimeGroups,
-            @NonNull UUID domainSetId) {
+    public PackageSetting(@NonNull String name, @Nullable String realName, @NonNull File path,
+                          int pkgFlags, int pkgPrivateFlags, @NonNull UUID domainSetId) {
         super(pkgFlags, pkgPrivateFlags);
         this.mName = name;
         this.mRealName = realName;
-        this.usesSdkLibraries = usesSdkLibraries;
-        this.usesSdkLibrariesVersionsMajor = usesSdkLibrariesVersionsMajor;
-        this.usesStaticLibraries = usesStaticLibraries;
-        this.usesStaticLibrariesVersions = usesStaticLibrariesVersions;
         this.mPath = path;
         this.mPathString = path.toString();
-        this.legacyNativeLibraryPath = legacyNativeLibraryPath;
-        this.mPrimaryCpuAbi = primaryCpuAbi;
-        this.mSecondaryCpuAbi = secondaryCpuAbi;
-        this.mCpuAbiOverride = cpuAbiOverride;
-        this.versionCode = longVersionCode;
         this.signatures = new PackageSignatures();
         this.installSource = InstallSource.EMPTY;
-        this.mSharedUserAppId = sharedUserAppId;
-        mDomainSetId = domainSetId;
-        copyMimeGroups(mimeGroups);
+        this.mDomainSetId = domainSetId;
         mSnapshot = makeCache();
     }
 
@@ -536,9 +518,10 @@
         return this;
     }
 
-    public void setSharedUserAppId(int sharedUserAppId) {
+    public PackageSetting setSharedUserAppId(int sharedUserAppId) {
         mSharedUserAppId = sharedUserAppId;
         onChanged();
+        return this;
     }
 
     public PackageSetting setIsPersistent(boolean isPersistent) {
@@ -576,7 +559,7 @@
                 + " " + mName + "/" + mAppId + "}";
     }
 
-    protected void copyMimeGroups(@Nullable Map<String, Set<String>> newMimeGroups) {
+    private void copyMimeGroups(@Nullable Map<String, Set<String>> newMimeGroups) {
         if (newMimeGroups == null) {
             mimeGroups = null;
             return;
@@ -1228,6 +1211,9 @@
             long activityInfoToken = proto.start(
                     PackageProto.UserInfoProto.ArchiveState.ACTIVITY_INFOS);
             proto.write(ArchiveActivityInfo.TITLE, activityInfo.getTitle());
+            proto.write(
+                    ArchiveActivityInfo.ORIGINAL_COMPONENT_NAME,
+                    activityInfo.getOriginalComponentName().flattenToString());
             if (activityInfo.getIconBitmap() != null) {
                 proto.write(ArchiveActivityInfo.ICON_BITMAP_PATH,
                         activityInfo.getIconBitmap().toAbsolutePath().toString());
@@ -1247,7 +1233,8 @@
     /**
      * @see #mPath
      */
-    PackageSetting setPath(@NonNull File path) {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public PackageSetting setPath(@NonNull File path) {
         this.mPath = path;
         this.mPathString = path.toString();
         onChanged();
@@ -1448,9 +1435,11 @@
         return this;
     }
 
-    public PackageSetting setMimeGroups(@NonNull Map<String, Set<String>> mimeGroups) {
-        this.mimeGroups = mimeGroups;
-        onChanged();
+    public PackageSetting setMimeGroups(@Nullable Map<String, Set<String>> mimeGroups) {
+        if (mimeGroups != null) {
+            copyMimeGroups(mimeGroups);
+            onChanged();
+        }
         return this;
     }
 
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 397a841..a39178e 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -368,6 +368,7 @@
     private static final String ATTR_VALUE = "value";
     private static final String ATTR_FIRST_INSTALL_TIME = "first-install-time";
     private static final String ATTR_ARCHIVE_ACTIVITY_TITLE = "activity-title";
+    private static final String ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME = "original-component-name";
     private static final String ATTR_ARCHIVE_INSTALLER_TITLE = "installer-title";
     private static final String ATTR_ARCHIVE_ICON_PATH = "icon-path";
     private static final String ATTR_ARCHIVE_MONOCHROME_ICON_PATH = "monochrome-icon-path";
@@ -930,16 +931,24 @@
             sharedUserSetting.mDisabledPackages.remove(p);
         }
         p.getPkgState().setUpdatedSystemApp(false);
-        PackageSetting ret = addPackageLPw(name, p.getRealName(), p.getPath(),
-                p.getLegacyNativeLibraryPath(), p.getPrimaryCpuAbiLegacy(),
-                p.getSecondaryCpuAbiLegacy(), p.getCpuAbiOverride(),
-                p.getAppId(), p.getVersionCode(), p.getFlags(), p.getPrivateFlags(),
-                p.getUsesSdkLibraries(), p.getUsesSdkLibrariesVersionsMajor(),
-                p.getUsesStaticLibraries(), p.getUsesStaticLibrariesVersions(), p.getMimeGroups(),
-                mDomainVerificationManager.generateNewId());
+        PackageSetting ret = addPackageLPw(name, p.getRealName(), p.getPath(), p.getAppId(),
+                p.getFlags(), p.getPrivateFlags(), mDomainVerificationManager.generateNewId());
         if (ret != null) {
+            ret.setLegacyNativeLibraryPath(p.getLegacyNativeLibraryPath());
+            ret.setPrimaryCpuAbi(p.getPrimaryCpuAbiLegacy());
+            ret.setSecondaryCpuAbi(p.getSecondaryCpuAbiLegacy());
+            ret.setCpuAbiOverride(p.getCpuAbiOverride());
+            ret.setLongVersionCode(p.getVersionCode());
+            ret.setUsesSdkLibraries(p.getUsesSdkLibraries());
+            ret.setUsesSdkLibrariesVersionsMajor(p.getUsesSdkLibrariesVersionsMajor());
+            ret.setUsesStaticLibraries(p.getUsesStaticLibraries());
+            ret.setUsesStaticLibrariesVersions(p.getUsesStaticLibrariesVersions());
+            ret.setMimeGroups(p.getMimeGroups());
             ret.setAppMetadataFilePath(p.getAppMetadataFilePath());
             ret.getPkgState().setUpdatedSystemApp(false);
+            ret.setIsPersistent(p.isPersistent());
+            ret.setTargetSdkVersion(p.getTargetSdkVersion());
+            ret.setRestrictUpdateHash(p.getRestrictUpdateHash());
         }
         mDisabledSysPackages.remove(name);
         return ret;
@@ -960,13 +969,8 @@
         }
     }
 
-    PackageSetting addPackageLPw(String name, String realName, File codePath,
-            String legacyNativeLibraryPathString, String primaryCpuAbiString,
-            String secondaryCpuAbiString, String cpuAbiOverrideString, int uid, long vc,
-            int pkgFlags, int pkgPrivateFlags, String[] usesSdkLibraries,
-            long[] usesSdkLibrariesVersions, String[] usesStaticLibraries,
-            long[] usesStaticLibrariesVersions, Map<String, Set<String>> mimeGroups,
-            @NonNull UUID domainSetId) {
+    PackageSetting addPackageLPw(String name, String realName, File codePath, int uid, int pkgFlags,
+                                 int pkgPrivateFlags, @NonNull UUID domainSetId) {
         PackageSetting p = mPackages.get(name);
         if (p != null) {
             if (p.getAppId() == uid) {
@@ -976,11 +980,8 @@
                     "Adding duplicate package, keeping first: " + name);
             return null;
         }
-        p = new PackageSetting(name, realName, codePath, legacyNativeLibraryPathString,
-                primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString, vc, pkgFlags,
-                pkgPrivateFlags, 0 /*userId*/, usesSdkLibraries, usesSdkLibrariesVersions,
-                usesStaticLibraries, usesStaticLibrariesVersions, mimeGroups, domainSetId);
-        p.setAppId(uid);
+        p = new PackageSetting(name, realName, codePath, pkgFlags, pkgPrivateFlags, domainSetId)
+                .setAppId(uid);
         if (mAppIds.registerExistingAppId(uid, p, name)) {
             mPackages.put(name, p);
             return p;
@@ -1091,16 +1092,21 @@
             int installUserId = installUser != null ? installUser.getIdentifier()
                     : UserHandle.USER_SYSTEM;
 
-            pkgSetting = new PackageSetting(pkgName, realPkgName, codePath,
-                    legacyNativeLibraryPath, primaryCpuAbi, secondaryCpuAbi,
-                    null /*cpuAbiOverrideString*/, versionCode, pkgFlags, pkgPrivateFlags,
-                    0 /*sharedUserAppId*/, usesSdkLibraries, usesSdkLibrariesVersions,
-                    usesStaticLibraries, usesStaticLibrariesVersions,
-                    createMimeGroups(mimeGroupNames), domainSetId)
+            pkgSetting = new PackageSetting(pkgName, realPkgName, codePath, pkgFlags,
+                    pkgPrivateFlags, domainSetId)
+                    .setUsesSdkLibraries(usesSdkLibraries)
+                    .setUsesSdkLibrariesVersionsMajor(usesSdkLibrariesVersions)
+                    .setUsesStaticLibraries(usesStaticLibraries)
+                    .setUsesStaticLibrariesVersions(usesStaticLibrariesVersions)
+                    .setLegacyNativeLibraryPath(legacyNativeLibraryPath)
+                    .setPrimaryCpuAbi(primaryCpuAbi)
+                    .setSecondaryCpuAbi(secondaryCpuAbi)
+                    .setLongVersionCode(versionCode)
+                    .setMimeGroups(createMimeGroups(mimeGroupNames))
                     .setIsPersistent(isPersistent)
                     .setTargetSdkVersion(targetSdkVersion)
-                    .setRestrictUpdateHash(restrictUpdatedHash);
-            pkgSetting.setLastModifiedTime(codePath.lastModified());
+                    .setRestrictUpdateHash(restrictUpdatedHash)
+                    .setLastModifiedTime(codePath.lastModified());
             if (sharedUser != null) {
                 pkgSetting.setSharedUserAppId(sharedUser.mAppId);
             }
@@ -2079,6 +2085,8 @@
             if (tagName.equals(TAG_ARCHIVE_ACTIVITY_INFO)) {
                 String title = parser.getAttributeValue(null,
                         ATTR_ARCHIVE_ACTIVITY_TITLE);
+                String originalComponentName =
+                        parser.getAttributeValue(null, ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME);
                 String iconAttribute = parser.getAttributeValue(null,
                         ATTR_ARCHIVE_ICON_PATH);
                 Path iconPath = iconAttribute == null ? null : Path.of(iconAttribute);
@@ -2087,17 +2095,27 @@
                 Path monochromeIconPath = monochromeAttribute == null ? null : Path.of(
                         monochromeAttribute);
 
-                if (title == null || iconPath == null) {
-                    Slog.wtf(TAG,
-                            TextUtils.formatSimple("Missing attributes in tag %s. %s: %s, %s: %s",
-                                    TAG_ARCHIVE_ACTIVITY_INFO, ATTR_ARCHIVE_ACTIVITY_TITLE, title,
+                if (title == null || originalComponentName == null || iconPath == null) {
+                    Slog.wtf(
+                            TAG,
+                            TextUtils.formatSimple(
+                                    "Missing attributes in tag %s. %s: %s, %s: %s, %s: %s",
+                                    TAG_ARCHIVE_ACTIVITY_INFO,
+                                    ATTR_ARCHIVE_ACTIVITY_TITLE,
+                                    title,
+                                    ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME,
+                                    originalComponentName,
                                     ATTR_ARCHIVE_ICON_PATH,
                                     iconPath));
                     continue;
                 }
 
                 activityInfos.add(
-                        new ArchiveState.ArchiveActivityInfo(title, iconPath, monochromeIconPath));
+                        new ArchiveState.ArchiveActivityInfo(
+                                title,
+                                ComponentName.unflattenFromString(originalComponentName),
+                                iconPath,
+                                monochromeIconPath));
             }
         }
         return activityInfos;
@@ -2469,6 +2487,10 @@
         for (ArchiveState.ArchiveActivityInfo activityInfo : archiveState.getActivityInfos()) {
             serializer.startTag(null, TAG_ARCHIVE_ACTIVITY_INFO);
             serializer.attribute(null, ATTR_ARCHIVE_ACTIVITY_TITLE, activityInfo.getTitle());
+            serializer.attribute(
+                    null,
+                    ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME,
+                    activityInfo.getOriginalComponentName().flattenToString());
             if (activityInfo.getIconBitmap() != null) {
                 serializer.attribute(null, ATTR_ARCHIVE_ICON_PATH,
                         activityInfo.getIconBitmap().toAbsolutePath().toString());
@@ -3049,6 +3071,12 @@
         serializer.attributeLongHex(null, "ft", pkg.getLastModifiedTime());
         serializer.attributeLongHex(null, "ut", pkg.getLastUpdateTime());
         serializer.attributeLong(null, "version", pkg.getVersionCode());
+        serializer.attributeBoolean(null, "isPersistent", pkg.isPersistent());
+        serializer.attributeInt(null, "targetSdkVersion", pkg.getTargetSdkVersion());
+        if (pkg.getRestrictUpdateHash() != null) {
+            serializer.attributeBytesBase64(null, "restrictUpdateHash",
+                    pkg.getRestrictUpdateHash());
+        }
         if (pkg.getLegacyNativeLibraryPath() != null) {
             serializer.attribute(null, "nativeLibraryPath", pkg.getLegacyNativeLibraryPath());
         }
@@ -3112,6 +3140,12 @@
         serializer.attributeLongHex(null, "ft", pkg.getLastModifiedTime());
         serializer.attributeLongHex(null, "ut", pkg.getLastUpdateTime());
         serializer.attributeLong(null, "version", pkg.getVersionCode());
+        serializer.attributeBoolean(null, "isPersistent", pkg.isPersistent());
+        serializer.attributeInt(null, "targetSdkVersion", pkg.getTargetSdkVersion());
+        if (pkg.getRestrictUpdateHash() != null) {
+            serializer.attributeBytesBase64(null, "restrictUpdateHash",
+                    pkg.getRestrictUpdateHash());
+        }
         if (!pkg.hasSharedUser()) {
             serializer.attributeInt(null, "userId", pkg.getAppId());
         } else {
@@ -3844,6 +3878,10 @@
         }
 
         long versionCode = parser.getAttributeLong(null, "version", 0);
+        boolean isPersistent = parser.getAttributeBoolean(null, "isPersistent", false);
+        int targetSdkVersion = parser.getAttributeInt(null, "targetSdkVersion", 0);
+        byte[] restrictUpdateHash = parser.getAttributeBytesBase64(null, "restrictUpdateHash",
+                null);
 
         int pkgFlags = 0;
         int pkgPrivateFlags = 0;
@@ -3856,10 +3894,16 @@
         // debug invalid entries. The actual logic for migrating to a new ID is done in other
         // methods that use DomainVerificationManagerInternal#generateNewId
         UUID domainSetId = DomainVerificationManagerInternal.DISABLED_ID;
-        PackageSetting ps = new PackageSetting(name, realName, new File(codePathStr),
-                legacyNativeLibraryPathStr, primaryCpuAbiStr, secondaryCpuAbiStr, cpuAbiOverrideStr,
-                versionCode, pkgFlags, pkgPrivateFlags, 0 /*sharedUserAppId*/, null, null, null,
-                null, null, domainSetId);
+        PackageSetting ps = new PackageSetting(name, realName, new File(codePathStr), pkgFlags,
+                pkgPrivateFlags, domainSetId)
+                .setLegacyNativeLibraryPath(legacyNativeLibraryPathStr)
+                .setPrimaryCpuAbi(primaryCpuAbiStr)
+                .setSecondaryCpuAbi(secondaryCpuAbiStr)
+                .setCpuAbiOverride(cpuAbiOverrideStr)
+                .setLongVersionCode(versionCode)
+                .setIsPersistent(isPersistent)
+                .setTargetSdkVersion(targetSdkVersion)
+                .setRestrictUpdateHash(restrictUpdateHash);
         long timeStamp = parser.getAttributeLongHex(null, "ft", 0);
         if (timeStamp == 0) {
             timeStamp = parser.getAttributeLong(null, "ts", 0);
@@ -3953,6 +3997,9 @@
         long loadingCompletedTime = 0;
         UUID domainSetId;
         String appMetadataFilePath = null;
+        boolean isPersistent = false;
+        int targetSdkVersion = 0;
+        byte[] restrictUpdateHash = null;
         try {
             name = parser.getAttributeValue(null, ATTR_NAME);
             realName = parser.getAttributeValue(null, "realName");
@@ -3976,6 +4023,9 @@
             }
 
             versionCode = parser.getAttributeLong(null, "version", 0);
+            isPersistent = parser.getAttributeBoolean(null, "isPersistent", false);
+            targetSdkVersion = parser.getAttributeInt(null, "targetSdkVersion", 0);
+            restrictUpdateHash = parser.getAttributeBytesBase64(null, "restrictUpdateHash", null);
             installerPackageName = parser.getAttributeValue(null, "installer");
             installerPackageUid = parser.getAttributeInt(null, "installerUid", INVALID_UID);
             updateOwnerPackageName = parser.getAttributeValue(null, "updateOwner");
@@ -4071,11 +4121,7 @@
                                 + parser.getPositionDescription());
             } else if (appId > 0) {
                 packageSetting = addPackageLPw(name.intern(), realName, new File(codePathStr),
-                        legacyNativeLibraryPathStr, primaryCpuAbiString, secondaryCpuAbiString,
-                        cpuAbiOverrideString, appId, versionCode, pkgFlags, pkgPrivateFlags,
-                        null /* usesSdkLibraries */, null /* usesSdkLibraryVersions */,
-                        null /* usesStaticLibraries */, null /* usesStaticLibraryVersions */,
-                        null /* mimeGroups */, domainSetId);
+                        appId, pkgFlags, pkgPrivateFlags, domainSetId);
                 if (PackageManagerService.DEBUG_SETTINGS)
                     Log.i(PackageManagerService.TAG, "Reading package " + name + ": appId="
                             + appId + " pkg=" + packageSetting);
@@ -4084,22 +4130,26 @@
                             + appId + " while parsing settings at "
                             + parser.getPositionDescription());
                 } else {
+                    packageSetting.setLegacyNativeLibraryPath(legacyNativeLibraryPathStr);
+                    packageSetting.setPrimaryCpuAbi(primaryCpuAbiString);
+                    packageSetting.setSecondaryCpuAbi(secondaryCpuAbiString);
+                    packageSetting.setCpuAbiOverride(cpuAbiOverrideString);
+                    packageSetting.setLongVersionCode(versionCode);
                     packageSetting.setLastModifiedTime(timeStamp);
                     packageSetting.setLastUpdateTime(lastUpdateTime);
                 }
             } else if (sharedUserAppId != 0) {
                 if (sharedUserAppId > 0) {
                     packageSetting = new PackageSetting(name.intern(), realName,
-                            new File(codePathStr), legacyNativeLibraryPathStr,
-                            primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString,
-                            versionCode, pkgFlags, pkgPrivateFlags, sharedUserAppId,
-                            null /* usesSdkLibraries */,
-                            null /* usesSdkLibrariesVersions */,
-                            null /* usesStaticLibraries */,
-                            null /* usesStaticLibraryVersions */,
-                            null /* mimeGroups */, domainSetId);
-                    packageSetting.setLastModifiedTime(timeStamp);
-                    packageSetting.setLastUpdateTime(lastUpdateTime);
+                            new File(codePathStr), pkgFlags, pkgPrivateFlags, domainSetId)
+                            .setLegacyNativeLibraryPath(legacyNativeLibraryPathStr)
+                            .setPrimaryCpuAbi(primaryCpuAbiString)
+                            .setSecondaryCpuAbi(secondaryCpuAbiString)
+                            .setCpuAbiOverride(cpuAbiOverrideString)
+                            .setLongVersionCode(versionCode)
+                            .setSharedUserAppId(sharedUserAppId)
+                            .setLastModifiedTime(timeStamp)
+                            .setLastUpdateTime(lastUpdateTime);
                     mPendingPackages.add(packageSetting);
                     if (PackageManagerService.DEBUG_SETTINGS)
                         Log.i(PackageManagerService.TAG, "Reading package " + name
@@ -4138,7 +4188,10 @@
                     .setForceQueryableOverride(installedForceQueryable)
                     .setLoadingProgress(loadingProgress)
                     .setLoadingCompletedTime(loadingCompletedTime)
-                    .setAppMetadataFilePath(appMetadataFilePath);
+                    .setAppMetadataFilePath(appMetadataFilePath)
+                    .setIsPersistent(isPersistent)
+                    .setTargetSdkVersion(targetSdkVersion)
+                    .setRestrictUpdateHash(restrictUpdateHash);
             // Handle legacy string here for single-user mode
             final String enabledStr = parser.getAttributeValue(null, ATTR_ENABLED);
             if (enabledStr != null) {
@@ -4899,9 +4952,11 @@
         }
         pw.print(prefix); pw.print("  versionCode="); pw.print(ps.getVersionCode());
         if (pkg != null) {
-            pw.print(" minSdk="); pw.print(pkg.getMinSdkVersion());
-            pw.print(" targetSdk="); pw.println(pkg.getTargetSdkVersion());
-
+            pw.print(" minSdk=");
+            pw.print(pkg.getMinSdkVersion());
+        }
+        pw.print(" targetSdk="); pw.println(ps.getTargetSdkVersion());
+        if (pkg != null) {
             SparseIntArray minExtensionVersions = pkg.getMinExtensionVersions();
 
             pw.print(prefix); pw.print("  minExtensionVersions=[");
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
index 9987867..585e2e4 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
@@ -16,6 +16,7 @@
 
 package com.android.server.pm;
 
+import static android.content.pm.Flags.sdkLibIndependence;
 import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
 import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_LIBRARY_BAD_CERTIFICATE_DIGEST;
 
@@ -951,10 +952,12 @@
             }
         }
         if (!pkg.getUsesSdkLibraries().isEmpty()) {
+            // Allow installation even if sdk-library dependency doesn't exist
+            boolean required = !sdkLibIndependence();
             usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesSdkLibraries(),
                     pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesCertDigests(),
-                    pkg.getPackageName(), "sdk", true, pkg.getTargetSdkVersion(), usesLibraryInfos,
-                    availablePackages, newLibraries);
+                    pkg.getPackageName(), "sdk", required, pkg.getTargetSdkVersion(),
+                    usesLibraryInfos, availablePackages, newLibraries);
         }
         return usesLibraryInfos;
     }
diff --git a/services/core/java/com/android/server/pm/pkg/ArchiveState.java b/services/core/java/com/android/server/pm/pkg/ArchiveState.java
index 4916a4a..1e40d44 100644
--- a/services/core/java/com/android/server/pm/pkg/ArchiveState.java
+++ b/services/core/java/com/android/server/pm/pkg/ArchiveState.java
@@ -16,9 +16,11 @@
 
 package com.android.server.pm.pkg;
 
+import android.content.ComponentName;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 
+import com.android.internal.util.AnnotationValidations;
 import com.android.internal.util.DataClass;
 
 import java.nio.file.Path;
@@ -56,6 +58,10 @@
         @NonNull
         private final String mTitle;
 
+        /** The component name of the original activity (pre-archival). */
+        @NonNull
+        private final ComponentName mOriginalComponentName;
+
         /**
          * The path to the stored icon of the activity in the app's locale. Null if the app does
          * not define any icon (default icon would be shown on the launcher).
@@ -96,11 +102,13 @@
         @DataClass.Generated.Member
         public ArchiveActivityInfo(
                 @NonNull String title,
+                @NonNull ComponentName originalComponentName,
                 @Nullable Path iconBitmap,
                 @Nullable Path monochromeIconBitmap) {
             this.mTitle = title;
-            com.android.internal.util.AnnotationValidations.validate(
-                    NonNull.class, null, mTitle);
+            this.mOriginalComponentName = originalComponentName;
+            AnnotationValidations.validate(NonNull.class, null, mTitle);
+            AnnotationValidations.validate(NonNull.class, null, mOriginalComponentName);
             this.mIconBitmap = iconBitmap;
             this.mMonochromeIconBitmap = monochromeIconBitmap;
 
@@ -116,6 +124,14 @@
         }
 
         /**
+         * The component name of the original activity (pre-archival).
+            */
+        @DataClass.Generated.Member
+        public @NonNull ComponentName getOriginalComponentName() {
+            return mOriginalComponentName;
+        }
+
+        /**
          * The path to the stored icon of the activity in the app's locale. Null if the app does
          * not define any icon (default icon would be shown on the launcher).
          */
@@ -140,6 +156,7 @@
 
             return "ArchiveActivityInfo { " +
                     "title = " + mTitle + ", " +
+                    "originalComponentName = " + mOriginalComponentName + ", " +
                     "iconBitmap = " + mIconBitmap + ", " +
                     "monochromeIconBitmap = " + mMonochromeIconBitmap +
             " }";
@@ -159,6 +176,7 @@
             //noinspection PointlessBooleanExpression
             return true
                     && java.util.Objects.equals(mTitle, that.mTitle)
+                    && java.util.Objects.equals(mOriginalComponentName, that.mOriginalComponentName)
                     && java.util.Objects.equals(mIconBitmap, that.mIconBitmap)
                     && java.util.Objects.equals(mMonochromeIconBitmap, that.mMonochromeIconBitmap);
         }
@@ -171,6 +189,7 @@
 
             int _hash = 1;
             _hash = 31 * _hash + java.util.Objects.hashCode(mTitle);
+            _hash = 31* _hash + java.util.Objects.hashCode(mOriginalComponentName);
             _hash = 31 * _hash + java.util.Objects.hashCode(mIconBitmap);
             _hash = 31 * _hash + java.util.Objects.hashCode(mMonochromeIconBitmap);
             return _hash;
@@ -180,7 +199,8 @@
                 time = 1693590309015L,
                 codegenVersion = "1.0.23",
                 sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/ArchiveState.java",
-                inputSignatures = "private final @android.annotation.NonNull java.lang.String mTitle\nprivate final @android.annotation.Nullable java.nio.file.Path mIconBitmap\nprivate final @android.annotation.Nullable java.nio.file.Path mMonochromeIconBitmap\nclass ArchiveActivityInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true)")
+                inputSignatures =
+                        "private final @android.annotation.NonNull java.lang.String mTitle\nprivate final @android.annotation.NonNull android.content.ComponentName mOriginalComponentName\nprivate final @android.annotation.Nullable java.nio.file.Path mIconBitmap\nprivate final @android.annotation.Nullable java.nio.file.Path mMonochromeIconBitmap\nclass ArchiveActivityInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true)")
         @Deprecated
         private void __metadata() {}
 
@@ -224,11 +244,9 @@
             @NonNull List<ArchiveActivityInfo> activityInfos,
             @NonNull String installerTitle) {
         this.mActivityInfos = activityInfos;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mActivityInfos);
+        AnnotationValidations.validate(NonNull.class, null, mActivityInfos);
         this.mInstallerTitle = installerTitle;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mInstallerTitle);
+        AnnotationValidations.validate(NonNull.class, null, mInstallerTitle);
 
         // onConstructed(); // You can define this method to get a callback
     }
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index d6e35e8..a33e353 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -347,8 +347,15 @@
                 UserHandle user = UserHandle.getUserHandleForUid(uid);
                 PermissionControllerManager manager = mPermControllerManagers.get(user);
                 if (manager == null) {
-                    manager = new PermissionControllerManager(
-                            getUserContext(getContext(), user), PermissionThread.getHandler());
+                    try {
+                        manager = new PermissionControllerManager(
+                                getUserContext(getContext(), user), PermissionThread.getHandler());
+                    } catch (IllegalArgumentException exception) {
+                        // There's a possible race condition when a user is being removed
+                        Log.e(LOG_TAG, "Could not create PermissionControllerManager for user"
+                                        + user, exception);
+                        return;
+                    }
                     mPermControllerManagers.put(user, manager);
                 }
                 manager.updateUserSensitiveForApp(uid);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 3a6664a..7c0fc99 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -139,6 +139,7 @@
 import android.os.FactoryTest;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeReason;
@@ -715,6 +716,11 @@
     private static final int MSG_LOG_KEYBOARD_SYSTEM_EVENT = 26;
 
     private class PolicyHandler extends Handler {
+
+        private PolicyHandler(Looper looper) {
+            super(looper);
+        }
+
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
@@ -2166,10 +2172,12 @@
     static class Injector {
         private final Context mContext;
         private final WindowManagerFuncs mWindowManagerFuncs;
+        private final Looper mLooper;
 
-        Injector(Context context, WindowManagerFuncs funcs) {
+        Injector(Context context, WindowManagerFuncs funcs, Looper looper) {
             mContext = context;
             mWindowManagerFuncs = funcs;
+            mLooper = looper;
         }
 
         Context getContext() {
@@ -2180,6 +2188,10 @@
             return mWindowManagerFuncs;
         }
 
+        Looper getLooper() {
+            return mLooper;
+        }
+
         AccessibilityShortcutController getAccessibilityShortcutController(
                 Context context, Handler handler, int initialUserId) {
             return new AccessibilityShortcutController(context, handler, initialUserId);
@@ -2208,7 +2220,7 @@
     /** {@inheritDoc} */
     @Override
     public void init(Context context, WindowManagerFuncs funcs) {
-        init(new Injector(context, funcs));
+        init(new Injector(context, funcs, Looper.myLooper()));
     }
 
     @VisibleForTesting
@@ -2284,7 +2296,7 @@
                     mContext, minHorizontal, maxHorizontal, minVertical, maxVertical, maxRadius);
         }
 
-        mHandler = new PolicyHandler();
+        mHandler = new PolicyHandler(injector.getLooper());
         mWakeGestureListener = new MyWakeGestureListener(mContext, mHandler);
         mSettingsObserver = new SettingsObserver(mHandler);
         mSettingsObserver.observe();
diff --git a/services/core/java/com/android/server/power/Android.bp b/services/core/java/com/android/server/power/Android.bp
index 1da9dd7..607d435 100644
--- a/services/core/java/com/android/server/power/Android.bp
+++ b/services/core/java/com/android/server/power/Android.bp
@@ -1,5 +1,5 @@
 aconfig_declarations {
-    name: "power_optimization_flags",
+    name: "backstage_power_flags",
     package: "com.android.server.power.optimization",
     srcs: [
         "stats/*.aconfig",
@@ -7,6 +7,6 @@
 }
 
 java_aconfig_library {
-    name: "power_optimization_flags_lib",
-    aconfig_declarations: "power_optimization_flags",
+    name: "backstage_power_flags_lib",
+    aconfig_declarations: "backstage_power_flags",
 }
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index dfbcbae6..4a4214f 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -3347,8 +3347,6 @@
             } else {
                 startDreaming = false;
             }
-            Slog.i(TAG, "handleSandman powerGroup=" + groupId + " startDreaming=" + startDreaming
-                    + " wakefulness=" + wakefulnessToString(wakefulness));
         }
 
         // Start dreaming if needed.
@@ -3383,23 +3381,19 @@
             if (startDreaming && isDreaming) {
                 mDreamsBatteryLevelDrain = 0;
                 if (wakefulness == WAKEFULNESS_DOZING) {
-                    Slog.i(TAG, "Dozing powerGroup " + groupId);
+                    Slog.i(TAG, "Dozing...");
                 } else {
-                    Slog.i(TAG, "Dreaming powerGroup " + groupId);
+                    Slog.i(TAG, "Dreaming...");
                 }
             }
 
             // If preconditions changed, wait for the next iteration to determine
             // whether the dream should continue (or be restarted).
             final PowerGroup powerGroup = mPowerGroups.get(groupId);
-            final int newWakefulness = powerGroup.getWakefulnessLocked();
             if (powerGroup.isSandmanSummonedLocked()
-                    || newWakefulness != wakefulness) {
+                    || powerGroup.getWakefulnessLocked() != wakefulness) {
                 return; // wait for next cycle
             }
-            Slog.i(TAG, "handleSandman powerGroup=" + groupId + " isDreaming=" + isDreaming
-                    + " wakefulness=" + newWakefulness);
-
 
             // Determine whether the dream should continue.
             long now = mClock.uptimeMillis();
diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig
index add806f..0f13571 100644
--- a/services/core/java/com/android/server/power/stats/flags.aconfig
+++ b/services/core/java/com/android/server/power/stats/flags.aconfig
@@ -2,14 +2,14 @@
 
 flag {
     name: "power_monitor_api"
-    namespace: "power_optimization"
+    namespace: "backstage_power"
     description: "Feature flag for ODPM API"
     bug: "295027807"
 }
 
 flag {
     name: "streamlined_battery_stats"
-    namespace: "power_optimization"
+    namespace: "backstage_power"
     description: "Feature flag for streamlined battery stats"
     bug: "285646152"
 }
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
index becbbf2..519acec 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
@@ -22,11 +22,10 @@
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.os.VibratorInfo;
+import android.os.vibrator.Flags;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.HapticFeedbackConstants;
-import android.view.flags.FeatureFlags;
-import android.view.flags.FeatureFlagsImpl;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -56,7 +55,8 @@
     // If present and valid, a vibration here will be used for an effect.
     // Otherwise, the system's default vibration will be used.
     @Nullable private final SparseArray<VibrationEffect> mHapticCustomizations;
-    private final FeatureFlags mViewFeatureFlags;
+
+    private float mKeyboardVibrationFixedAmplitude;
 
     /** @hide */
     public HapticFeedbackVibrationProvider(Resources res, Vibrator vibrator) {
@@ -65,16 +65,14 @@
 
     /** @hide */
     public HapticFeedbackVibrationProvider(Resources res, VibratorInfo vibratorInfo) {
-        this(res, vibratorInfo, loadHapticCustomizations(res, vibratorInfo),
-                new FeatureFlagsImpl());
+        this(res, vibratorInfo, loadHapticCustomizations(res, vibratorInfo));
     }
 
     /** @hide */
     @VisibleForTesting HapticFeedbackVibrationProvider(
             Resources res,
             VibratorInfo vibratorInfo,
-            @Nullable SparseArray<VibrationEffect> hapticCustomizations,
-            FeatureFlags viewFeatureFlags) {
+            @Nullable SparseArray<VibrationEffect> hapticCustomizations) {
         mVibratorInfo = vibratorInfo;
         mHapticTextHandleEnabled = res.getBoolean(
                 com.android.internal.R.bool.config_enableHapticTextHandle);
@@ -83,14 +81,17 @@
             hapticCustomizations = null;
         }
         mHapticCustomizations = hapticCustomizations;
-        mViewFeatureFlags = viewFeatureFlags;
-
         mSafeModeEnabledVibrationEffect =
                 effectHasCustomization(HapticFeedbackConstants.SAFE_MODE_ENABLED)
                         ? mHapticCustomizations.get(HapticFeedbackConstants.SAFE_MODE_ENABLED)
                         : VibrationSettings.createEffectFromResource(
                                 res,
                                 com.android.internal.R.array.config_safeModeEnabledVibePattern);
+        mKeyboardVibrationFixedAmplitude = res.getFloat(
+                com.android.internal.R.dimen.config_keyboardHapticFeedbackFixedAmplitude);
+        if (mKeyboardVibrationFixedAmplitude < 0 || mKeyboardVibrationFixedAmplitude > 1) {
+            mKeyboardVibrationFixedAmplitude = -1;
+        }
     }
 
     /**
@@ -120,6 +121,9 @@
                 return getVibration(effectId, VibrationEffect.EFFECT_TEXTURE_TICK);
 
             case HapticFeedbackConstants.KEYBOARD_RELEASE:
+            case HapticFeedbackConstants.KEYBOARD_TAP: // == KEYBOARD_PRESS
+                return getKeyboardVibration(effectId);
+
             case HapticFeedbackConstants.VIRTUAL_KEY_RELEASE:
             case HapticFeedbackConstants.ENTRY_BUMP:
             case HapticFeedbackConstants.DRAG_CROSSING:
@@ -128,7 +132,6 @@
                         VibrationEffect.EFFECT_TICK,
                         /* fallbackForPredefinedEffect= */ false);
 
-            case HapticFeedbackConstants.KEYBOARD_TAP: // == KEYBOARD_PRESS
             case HapticFeedbackConstants.VIRTUAL_KEY:
             case HapticFeedbackConstants.EDGE_RELEASE:
             case HapticFeedbackConstants.CALENDAR_DATE:
@@ -204,6 +207,10 @@
             case HapticFeedbackConstants.SCROLL_LIMIT:
                 attrs = HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES;
                 break;
+            case HapticFeedbackConstants.KEYBOARD_TAP:
+            case HapticFeedbackConstants.KEYBOARD_RELEASE:
+                attrs = createKeyboardVibrationAttributes();
+                break;
             default:
                 attrs = TOUCH_VIBRATION_ATTRIBUTES;
         }
@@ -212,9 +219,12 @@
         if (bypassVibrationIntensitySetting) {
             flags |= VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF;
         }
-        if (shouldBypassInterruptionPolicy(effectId, mViewFeatureFlags)) {
+        if (shouldBypassInterruptionPolicy(effectId)) {
             flags |= VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
         }
+        if (shouldBypassIntensityScale(effectId)) {
+            flags |= VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE;
+        }
 
         return flags == 0 ? attrs : new VibrationAttributes.Builder(attrs).setFlags(flags).build();
     }
@@ -295,6 +305,64 @@
         return mHapticCustomizations != null && mHapticCustomizations.contains(effectId);
     }
 
+    private VibrationEffect getKeyboardVibration(int effectId) {
+        if (effectHasCustomization(effectId)) {
+            return mHapticCustomizations.get(effectId);
+        }
+
+        int primitiveId;
+        int predefinedEffectId;
+        boolean predefinedEffectFallback;
+
+        switch (effectId) {
+            case HapticFeedbackConstants.KEYBOARD_RELEASE:
+                primitiveId = VibrationEffect.Composition.PRIMITIVE_TICK;
+                predefinedEffectId = VibrationEffect.EFFECT_TICK;
+                predefinedEffectFallback = false;
+                break;
+            case HapticFeedbackConstants.KEYBOARD_TAP:
+            default:
+                primitiveId = VibrationEffect.Composition.PRIMITIVE_CLICK;
+                predefinedEffectId = VibrationEffect.EFFECT_CLICK;
+                predefinedEffectFallback = true;
+        }
+        if (Flags.keyboardCategoryEnabled() && mKeyboardVibrationFixedAmplitude > 0) {
+            if (mVibratorInfo.isPrimitiveSupported(primitiveId)) {
+                return VibrationEffect.startComposition()
+                        .addPrimitive(primitiveId, mKeyboardVibrationFixedAmplitude)
+                        .compose();
+            }
+        }
+        return getVibration(effectId, predefinedEffectId,
+                /* fallbackForPredefinedEffect= */ predefinedEffectFallback);
+    }
+
+    private boolean shouldBypassIntensityScale(int effectId) {
+        if (!Flags.keyboardCategoryEnabled() || mKeyboardVibrationFixedAmplitude < 0) {
+            // shouldn't bypass if not support keyboard category or no fixed amplitude
+            return false;
+        }
+        switch (effectId) {
+            case HapticFeedbackConstants.KEYBOARD_TAP:
+                return mVibratorInfo.isPrimitiveSupported(
+                        VibrationEffect.Composition.PRIMITIVE_CLICK);
+            case HapticFeedbackConstants.KEYBOARD_RELEASE:
+                return mVibratorInfo.isPrimitiveSupported(
+                        VibrationEffect.Composition.PRIMITIVE_TICK);
+        }
+        return false;
+    }
+
+    private static VibrationAttributes createKeyboardVibrationAttributes() {
+        if (!Flags.keyboardCategoryEnabled()) {
+            return TOUCH_VIBRATION_ATTRIBUTES;
+        }
+
+        return new VibrationAttributes.Builder(TOUCH_VIBRATION_ATTRIBUTES)
+                .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
+                .build();
+    }
+
     @Nullable
     private static SparseArray<VibrationEffect> loadHapticCustomizations(
             Resources res, VibratorInfo vibratorInfo) {
@@ -306,8 +374,7 @@
         }
     }
 
-    private static boolean shouldBypassInterruptionPolicy(
-            int effectId, FeatureFlags viewFeatureFlags) {
+    private static boolean shouldBypassInterruptionPolicy(int effectId) {
         switch (effectId) {
             case HapticFeedbackConstants.SCROLL_TICK:
             case HapticFeedbackConstants.SCROLL_ITEM_FOCUS:
@@ -315,7 +382,7 @@
                 // The SCROLL_* constants should bypass interruption filter, so that scroll haptics
                 // can play regardless of focus modes like DND. Guard this behavior by the feature
                 // flag controlling the general scroll feedback APIs.
-                return viewFeatureFlags.scrollFeedbackApi();
+                return android.view.flags.Flags.scrollFeedbackApi();
             default:
                 return false;
         }
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index db8a9ae..7f55836 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -16,6 +16,7 @@
 
 package com.android.server.vibrator;
 
+import static android.os.VibrationAttributes.CATEGORY_KEYBOARD;
 import static android.os.VibrationAttributes.USAGE_ACCESSIBILITY;
 import static android.os.VibrationAttributes.USAGE_ALARM;
 import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
@@ -52,6 +53,7 @@
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.os.Vibrator.VibrationIntensity;
+import android.os.vibrator.Flags;
 import android.os.vibrator.VibrationConfig;
 import android.provider.Settings;
 import android.util.IndentingPrintWriter;
@@ -188,6 +190,8 @@
     @GuardedBy("mLock")
     private boolean mVibrateOn;
     @GuardedBy("mLock")
+    private boolean mKeyboardVibrationOn;
+    @GuardedBy("mLock")
     private int mRingerMode;
     @GuardedBy("mLock")
     private boolean mOnWirelessCharger;
@@ -295,6 +299,8 @@
                 Settings.System.getUriFor(Settings.System.NOTIFICATION_VIBRATION_INTENSITY));
         registerSettingsObserver(
                 Settings.System.getUriFor(Settings.System.RING_VIBRATION_INTENSITY));
+        registerSettingsObserver(
+                Settings.System.getUriFor(Settings.System.KEYBOARD_VIBRATION_ENABLED));
 
         if (mVibrationConfig.ignoreVibrationsOnWirelessCharger()) {
             Intent batteryStatus = mContext.registerReceiver(
@@ -418,14 +424,9 @@
             }
 
             if (!callerInfo.attrs.isFlagSet(
-                    VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)) {
-                if (!mVibrateOn && (VIBRATE_ON_DISABLED_USAGE_ALLOWED != usage)) {
-                    return Vibration.Status.IGNORED_FOR_SETTINGS;
-                }
-
-                if (getCurrentIntensity(usage) == Vibrator.VIBRATION_INTENSITY_OFF) {
-                    return Vibration.Status.IGNORED_FOR_SETTINGS;
-                }
+                    VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)
+                    && !shouldVibrateForUserSetting(callerInfo)) {
+                return Vibration.Status.IGNORED_FOR_SETTINGS;
             }
 
             if (!callerInfo.attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)) {
@@ -497,6 +498,30 @@
         return mRingerMode != AudioManager.RINGER_MODE_SILENT;
     }
 
+    /**
+     * Return {@code true} if the device should vibrate for user setting, and
+     * {@code false} to ignore the vibration.
+     */
+    @GuardedBy("mLock")
+    private boolean shouldVibrateForUserSetting(Vibration.CallerInfo callerInfo) {
+        final int usage = callerInfo.attrs.getUsage();
+        if (!mVibrateOn && (VIBRATE_ON_DISABLED_USAGE_ALLOWED != usage)) {
+            // Main setting disabled.
+            return false;
+        }
+
+        if (Flags.keyboardCategoryEnabled()) {
+            int category = callerInfo.attrs.getCategory();
+            if (usage == USAGE_TOUCH && category == CATEGORY_KEYBOARD) {
+                // Keyboard touch has a different user setting.
+                return mKeyboardVibrationOn;
+            }
+        }
+
+        // Apply individual user setting based on usage.
+        return getCurrentIntensity(usage) != Vibrator.VIBRATION_INTENSITY_OFF;
+    }
+
     /** Update all cached settings and triggers registered listeners. */
     void update() {
         updateSettings();
@@ -508,6 +533,8 @@
         synchronized (mLock) {
             mVibrateInputDevices = loadSystemSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0) > 0;
             mVibrateOn = loadSystemSetting(Settings.System.VIBRATE_ON, 1) > 0;
+            mKeyboardVibrationOn = loadSystemSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED,
+                    mVibrationConfig.isDefaultKeyboardVibrationEnabled() ? 1 : 0) > 0;
 
             int alarmIntensity = toIntensity(
                     loadSystemSetting(Settings.System.ALARM_VIBRATION_INTENSITY, -1),
@@ -806,18 +833,24 @@
         private final SparseArray<Integer> mProcStatesCache = new SparseArray<>();
 
         public boolean isUidForeground(int uid) {
-            return mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
-                    <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+            synchronized (this) {
+                return mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
+                        <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+            }
         }
 
         @Override
         public void onUidGone(int uid, boolean disabled) {
-            mProcStatesCache.delete(uid);
+            synchronized (this) {
+                mProcStatesCache.delete(uid);
+            }
         }
 
         @Override
         public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
-            mProcStatesCache.put(uid, procState);
+            synchronized (this) {
+                mProcStatesCache.put(uid, procState);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 45bd152..ace7777 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -97,7 +97,8 @@
             new VibrationAttributes.Builder().build();
     private static final int ATTRIBUTES_ALL_BYPASS_FLAGS =
             VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY
-                    | VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF;
+                    | VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF
+                    | VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE;
 
     /** Fixed large duration used to note repeating vibrations to {@link IBatteryStats}. */
     private static final long BATTERY_STATS_REPEATING_VIBRATION_DURATION = 5_000;
@@ -771,8 +772,11 @@
     private Vibration.EndInfo startVibrationLocked(HalVibration vib) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
         try {
-            // Scale effect before dispatching it to the input devices or the vibration thread.
-            vib.scaleEffects(mVibrationScaler::scale);
+            if (!vib.callerInfo.attrs.isFlagSet(
+                    VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)) {
+                // Scale effect before dispatching it to the input devices or the vibration thread.
+                vib.scaleEffects(mVibrationScaler::scale);
+            }
             boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable(
                     vib.callerInfo, vib.getEffectToPlay());
             if (inputDevicesAvailable) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index c54e3bd..5f8bbe5 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -29,7 +29,6 @@
 
 import android.annotation.Nullable;
 import android.app.WallpaperColors;
-import android.app.WallpaperManager;
 import android.app.WallpaperManager.SetWallpaperFlags;
 import android.app.backup.WallpaperBackupHelper;
 import android.content.ComponentName;
@@ -38,7 +37,6 @@
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.os.FileUtils;
-import android.os.SystemProperties;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.Xml;
@@ -77,8 +75,6 @@
     private final WallpaperCropper mWallpaperCropper;
     private final Context mContext;
 
-    private final boolean mIsLockscreenLiveWallpaperEnabled;
-
     WallpaperDataParser(Context context, WallpaperDisplayHelper wallpaperDisplayHelper,
             WallpaperCropper wallpaperCropper) {
         mContext = context;
@@ -86,8 +82,6 @@
         mWallpaperCropper = wallpaperCropper;
         mImageWallpaper = ComponentName.unflattenFromString(
                 context.getResources().getString(R.string.image_wallpaper_component));
-        mIsLockscreenLiveWallpaperEnabled =
-                SystemProperties.getBoolean("persist.wm.debug.lockscreen_live_wallpaper", true);
     }
 
     private JournaledFile makeJournaledFile(int userId) {
@@ -127,42 +121,26 @@
     }
 
     /**
-     * TODO(b/197814683) adapt comment once flag is removed
-     *
      * Load the system wallpaper (and the lock wallpaper, if it exists) from disk
      * @param userId the id of the user for which the wallpaper should be loaded
      * @param keepDimensionHints if false, parse and set the
      *                      {@link DisplayData} width and height for the specified userId
-     * @param wallpaper the wallpaper object to reuse to do the modifications.
-     *                      If null, a new object will be created.
-     * @param lockWallpaper the lock wallpaper object to reuse to do the modifications.
-     *                      If null, a new object will be created.
-     * @param which The wallpaper(s) to load. Only has effect if
-     *                      {@link WallpaperManager#isLockscreenLiveWallpaperEnabled} is true,
-     *                      otherwise both wallpaper will always be loaded.
+     * @param migrateFromOld whether the current wallpaper is pre-N and needs migration
+     * @param which The wallpaper(s) to load.
      * @return a {@link WallpaperLoadingResult} object containing the wallpaper data.
-     *                      This object will contain the {@code wallpaper} and
-     *                      {@code lockWallpaper} provided as parameters, if they are not null.
      */
     public WallpaperLoadingResult loadSettingsLocked(int userId, boolean keepDimensionHints,
-            WallpaperData wallpaper, WallpaperData lockWallpaper, @SetWallpaperFlags int which) {
+            boolean migrateFromOld, @SetWallpaperFlags int which) {
         JournaledFile journal = makeJournaledFile(userId);
         FileInputStream stream = null;
         File file = journal.chooseForRead();
 
-        boolean migrateFromOld = wallpaper == null;
+        boolean loadSystem = (which & FLAG_SYSTEM) != 0;
+        boolean loadLock = (which & FLAG_LOCK) != 0;
+        WallpaperData wallpaper = null;
+        WallpaperData lockWallpaper = null;
 
-        boolean separateLockscreenEngine = mIsLockscreenLiveWallpaperEnabled;
-        boolean loadSystem = !separateLockscreenEngine || (which & FLAG_SYSTEM) != 0;
-        boolean loadLock = !separateLockscreenEngine || (which & FLAG_LOCK) != 0;
-
-        // don't reuse the wallpaper objects in the new version
-        if (separateLockscreenEngine) {
-            wallpaper = null;
-            lockWallpaper = null;
-        }
-
-        if (wallpaper == null && loadSystem) {
+        if (loadSystem) {
             // Do this once per boot
             if (migrateFromOld) migrateFromOld();
             wallpaper = new WallpaperData(userId, FLAG_SYSTEM);
@@ -188,11 +166,8 @@
                 type = parser.next();
                 if (type == XmlPullParser.START_TAG) {
                     String tag = parser.getName();
-                    if (("wp".equals(tag) && loadSystem)
-                            || ("kwp".equals(tag) && mIsLockscreenLiveWallpaperEnabled
-                                && loadLock)) {
-
-                        if ("kwp".equals(tag) && lockWallpaper == null) {
+                    if (("wp".equals(tag) && loadSystem) || ("kwp".equals(tag) && loadLock)) {
+                        if ("kwp".equals(tag)) {
                             lockWallpaper = new WallpaperData(userId, FLAG_LOCK);
                         }
                         WallpaperData wallpaperToParse =
@@ -219,12 +194,6 @@
                             Slog.v(TAG, "mNextWallpaperComponent:"
                                     + wallpaper.nextWallpaperComponent);
                         }
-                    } else if ("kwp".equals(tag) && !mIsLockscreenLiveWallpaperEnabled) {
-                        // keyguard-specific wallpaper for this user (legacy code)
-                        if (lockWallpaper == null) {
-                            lockWallpaper = new WallpaperData(userId, FLAG_LOCK);
-                        }
-                        parseWallpaperAttributes(parser, lockWallpaper, false);
                     }
                 }
             } while (type != XmlPullParser.END_DOCUMENT);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 0718f2f..bdcde66 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -51,6 +51,7 @@
 import android.app.IWallpaperManager;
 import android.app.IWallpaperManagerCallback;
 import android.app.PendingIntent;
+import android.app.UidObserver;
 import android.app.UserSwitchObserver;
 import android.app.WallpaperColors;
 import android.app.WallpaperInfo;
@@ -103,6 +104,7 @@
 import android.system.ErrnoException;
 import android.system.Os;
 import android.util.EventLog;
+import android.util.IntArray;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -120,6 +122,7 @@
 import com.android.server.SystemService;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.utils.TimingsTraceAndSlog;
+import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
 import org.xmlpull.v1.XmlPullParserException;
@@ -185,8 +188,6 @@
     }
 
     private final Object mLock = new Object();
-    /** True to enable a second engine for lock screen wallpaper when different from system wp. */
-    private final boolean mIsLockscreenLiveWallpaperEnabled;
     /** True to support different crops for different display dimensions */
     private final boolean mIsMultiCropEnabled;
     /** Tracks wallpaper being migrated from system+lock to lock when setting static wp. */
@@ -227,7 +228,7 @@
             mWallpaperLockFile = new File(mWallpaperDir, WALLPAPER_LOCK_ORIG);
         }
 
-        WallpaperData dataForEvent(boolean sysChanged, boolean lockChanged) {
+        WallpaperData dataForEvent(boolean lockChanged) {
             WallpaperData wallpaper = null;
             synchronized (mLock) {
                 if (lockChanged) {
@@ -249,7 +250,7 @@
             final File changedFile = new File(mWallpaperDir, path);
             final boolean sysWallpaperChanged = (mWallpaperFile.equals(changedFile));
             final boolean lockWallpaperChanged = (mWallpaperLockFile.equals(changedFile));
-            final WallpaperData wallpaper = dataForEvent(sysWallpaperChanged, lockWallpaperChanged);
+            final WallpaperData wallpaper = dataForEvent(lockWallpaperChanged);
 
             final boolean moved = (event == MOVED_TO);
             final boolean written = (event == CLOSE_WRITE || moved);
@@ -375,7 +376,7 @@
                 }
 
                 saveSettingsLocked(wallpaper.userId);
-                if ((sysWallpaperChanged || lockWallpaperChanged) && localSync != null) {
+                if (localSync != null) {
                     localSync.complete();
                 }
             }
@@ -386,129 +387,9 @@
             }
         }
 
-        // Handles static wallpaper changes generated by WallpaperObserver events when
-        // enableSeparateLockScreenEngine() is false.
-        // TODO(b/266818039) Remove this method
-        private void updateWallpapersLegacy(int event, String path) {
-            final boolean moved = (event == MOVED_TO);
-            final boolean written = (event == CLOSE_WRITE || moved);
-            final File changedFile = new File(mWallpaperDir, path);
-
-            // System and system+lock changes happen on the system wallpaper input file;
-            // lock-only changes happen on the dedicated lock wallpaper input file
-            final boolean sysWallpaperChanged = (mWallpaperFile.equals(changedFile));
-            final boolean lockWallpaperChanged = (mWallpaperLockFile.equals(changedFile));
-            int notifyColorsWhich = 0;
-            WallpaperData wallpaper = dataForEvent(sysWallpaperChanged, lockWallpaperChanged);
-
-            if (DEBUG) {
-                Slog.v(TAG, "Wallpaper file change: evt=" + event
-                        + " path=" + path
-                        + " sys=" + sysWallpaperChanged
-                        + " lock=" + lockWallpaperChanged
-                        + " imagePending=" + wallpaper.imageWallpaperPending
-                        + " mWhich=0x" + Integer.toHexString(wallpaper.mWhich)
-                        + " written=" + written);
-            }
-
-            if (moved && lockWallpaperChanged) {
-                // We just migrated sys -> lock to preserve imagery for an impending
-                // new system-only wallpaper.  Tell keyguard about it and make sure it
-                // has the right SELinux label.
-                if (DEBUG) {
-                    Slog.i(TAG, "Sys -> lock MOVED_TO");
-                }
-                SELinux.restorecon(changedFile);
-                notifyLockWallpaperChanged();
-                notifyWallpaperColorsChanged(wallpaper, FLAG_LOCK);
-                return;
-            }
-
-            synchronized (mLock) {
-                if (sysWallpaperChanged || lockWallpaperChanged) {
-                    notifyCallbacksLocked(wallpaper);
-                    if (wallpaper.wallpaperComponent == null
-                            || event != CLOSE_WRITE // includes the MOVED_TO case
-                            || wallpaper.imageWallpaperPending) {
-                        if (written) {
-                            // The image source has finished writing the source image,
-                            // so we now produce the crop rect (in the background), and
-                            // only publish the new displayable (sub)image as a result
-                            // of that work.
-                            if (DEBUG) {
-                                Slog.v(TAG, "Wallpaper written; generating crop");
-                            }
-                            SELinux.restorecon(changedFile);
-                            if (moved) {
-                                // This is a restore, so generate the crop using any just-restored new
-                                // crop guidelines, making sure to preserve our local dimension hints.
-                                // We also make sure to reapply the correct SELinux label.
-                                if (DEBUG) {
-                                    Slog.v(TAG, "moved-to, therefore restore; reloading metadata");
-                                }
-                                loadSettingsLocked(wallpaper.userId, true, FLAG_SYSTEM | FLAG_LOCK);
-                            }
-                            mWallpaperCropper.generateCrop(wallpaper);
-                            if (DEBUG) {
-                                Slog.v(TAG, "Crop done; invoking completion callback");
-                            }
-                            wallpaper.imageWallpaperPending = false;
-                            if (sysWallpaperChanged) {
-                                IRemoteCallback.Stub callback = new IRemoteCallback.Stub() {
-                                    @Override
-                                    public void sendResult(Bundle data) throws RemoteException {
-                                        Slog.d(TAG, "publish system wallpaper changed!");
-                                        notifyWallpaperChanged(wallpaper);
-                                    }
-                                };
-                                // If this was the system wallpaper, rebind...
-                                bindWallpaperComponentLocked(mImageWallpaper, true,
-                                        false, wallpaper, callback);
-                                notifyColorsWhich |= FLAG_SYSTEM;
-                            }
-                            if (lockWallpaperChanged
-                                    || (wallpaper.mWhich & FLAG_LOCK) != 0) {
-                                if (DEBUG) {
-                                    Slog.i(TAG, "Lock-relevant wallpaper changed");
-                                }
-                                // either a lock-only wallpaper commit or a system+lock event.
-                                // if it's system-plus-lock we need to wipe the lock bookkeeping;
-                                // we're falling back to displaying the system wallpaper there.
-                                if (!lockWallpaperChanged) {
-                                    mLockWallpaperMap.remove(wallpaper.userId);
-                                }
-                                // and in any case, tell keyguard about it
-                                notifyLockWallpaperChanged();
-                                notifyColorsWhich |= FLAG_LOCK;
-                            }
-
-                            saveSettingsLocked(wallpaper.userId);
-                            // Notify the client immediately if only lockscreen wallpaper changed.
-                            if (lockWallpaperChanged && !sysWallpaperChanged) {
-                                notifyWallpaperChanged(wallpaper);
-                            }
-                        }
-                    }
-                }
-            }
-
-            // Outside of the lock since it will synchronize itself
-            if (notifyColorsWhich != 0) {
-                notifyWallpaperColorsChanged(wallpaper, notifyColorsWhich);
-            }
-        }
-
         @Override
         public void onEvent(int event, String path) {
-            if (path == null) {
-                return;
-            }
-
-            if (mIsLockscreenLiveWallpaperEnabled) {
-                updateWallpapers(event, path);
-            } else {
-                updateWallpapersLegacy(event, path);
-            }
+            if (path != null) updateWallpapers(event, path);
         }
     }
 
@@ -525,17 +406,6 @@
         }
     }
 
-    private void notifyLockWallpaperChanged() {
-        final IWallpaperManagerCallback cb = mKeyguardListener;
-        if (cb != null) {
-            try {
-                cb.onWallpaperChanged();
-            } catch (RemoteException e) {
-                Slog.w(TAG, "Failed to notify keyguard callback about wallpaper changes", e);
-            }
-        }
-    }
-
     void notifyWallpaperColorsChanged(@NonNull WallpaperData wallpaper, int which) {
         if (DEBUG) {
             Slog.i(TAG, "Notifying wallpaper colors changed");
@@ -594,14 +464,12 @@
 
     private void notifyColorListeners(@NonNull WallpaperColors wallpaperColors, int which,
             int userId, int displayId) {
-        final IWallpaperManagerCallback keyguardListener;
         final ArrayList<IWallpaperManagerCallback> colorListeners = new ArrayList<>();
         synchronized (mLock) {
             final RemoteCallbackList<IWallpaperManagerCallback> currentUserColorListeners =
                     getWallpaperCallbacks(userId, displayId);
             final RemoteCallbackList<IWallpaperManagerCallback> userAllColorListeners =
                     getWallpaperCallbacks(UserHandle.USER_ALL, displayId);
-            keyguardListener = mKeyguardListener;
 
             if (currentUserColorListeners != null) {
                 final int count = currentUserColorListeners.beginBroadcast();
@@ -630,15 +498,6 @@
                 Slog.w(TAG, "onWallpaperColorsChanged() threw an exception", e);
             }
         }
-
-        // Only shows Keyguard on default display
-        if (keyguardListener != null && displayId == DEFAULT_DISPLAY) {
-            try {
-                keyguardListener.onWallpaperColorsChanged(wallpaperColors, which, userId);
-            } catch (RemoteException e) {
-                Slog.w(TAG, "keyguardListener.onWallpaperColorsChanged threw an exception", e);
-            }
-        }
     }
 
     /**
@@ -759,8 +618,6 @@
     private final MyPackageMonitor mMonitor;
     private final AppOpsManager mAppOpsManager;
 
-    // TODO("b/264637309") probably move this in WallpaperDisplayUtils,
-    //  after logic is changed for the lockscreen lwp project
     private final DisplayManager.DisplayListener mDisplayListener =
             new DisplayManager.DisplayListener() {
 
@@ -811,8 +668,6 @@
     protected WallpaperData mLastWallpaper;
     // The currently bound lock screen only wallpaper, or null if none
     protected WallpaperData mLastLockWallpaper;
-    private IWallpaperManagerCallback mKeyguardListener;
-    private boolean mWaitingForUnlock;
 
     /**
      * Flag set to true after reboot if the home wallpaper is waiting for the device to be unlocked.
@@ -1014,8 +869,8 @@
                 if (!mWallpaper.wallpaperUpdating && mWallpaper.userId == mCurrentUserId) {
                     Slog.w(TAG, "Wallpaper reconnect timed out for " + mWallpaper.wallpaperComponent
                             + ", reverting to built-in wallpaper!");
-                    int which = mIsLockscreenLiveWallpaperEnabled ? mWallpaper.mWhich : FLAG_SYSTEM;
-                    clearWallpaperLocked(which, mWallpaper.userId, null);
+                    int which = mWallpaper.mWhich;
+                    clearWallpaperLocked(which, mWallpaper.userId, false, null);
                 }
             }
         };
@@ -1195,7 +1050,7 @@
                 } else {
                     // Timeout
                     Slog.w(TAG, "Reverting to built-in wallpaper!");
-                    clearWallpaperLocked(mWallpaper.mWhich, mWallpaper.userId, null);
+                    clearWallpaperLocked(mWallpaper.mWhich, mWallpaper.userId, false, null);
                     final String flattened = wpService.flattenToString();
                     EventLog.writeEvent(EventLogTags.WP_WALLPAPER_CRASHED,
                             flattened.substring(0, Math.min(flattened.length(),
@@ -1235,7 +1090,7 @@
                                 if (mLmkLimitRebindRetries <= 0) {
                                     Slog.w(TAG, "Reverting to built-in wallpaper due to lmk!");
                                     clearWallpaperLocked(
-                                            mWallpaper.mWhich, mWallpaper.userId, null);
+                                            mWallpaper.mWhich, mWallpaper.userId, false, null);
                                     mLmkLimitRebindRetries = LMK_RECONNECT_REBIND_RETRIES;
                                     return;
                                 }
@@ -1254,7 +1109,7 @@
                                     && mWallpaper.lastDiedTime + MIN_WALLPAPER_CRASH_TIME
                                     > SystemClock.uptimeMillis()) {
                                 Slog.w(TAG, "Reverting to built-in wallpaper!");
-                                clearWallpaperLocked(FLAG_SYSTEM, mWallpaper.userId, null);
+                                clearWallpaperLocked(FLAG_SYSTEM, mWallpaper.userId, false, null);
                             } else {
                                 mWallpaper.lastDiedTime = SystemClock.uptimeMillis();
                                 tryToRebind();
@@ -1291,19 +1146,8 @@
                 if (mImageWallpaper.equals(mWallpaper.wallpaperComponent)) {
                     return;
                 }
-
-                // Live wallpapers always are system wallpapers unless lock screen live wp is
-                // enabled.
-                which = mIsLockscreenLiveWallpaperEnabled ? mWallpaper.mWhich : FLAG_SYSTEM;
+                which = mWallpaper.mWhich;
                 mWallpaper.primaryColors = primaryColors;
-
-                // It's also the lock screen wallpaper when we don't have a bitmap in there.
-                if (displayId == DEFAULT_DISPLAY) {
-                    final WallpaperData lockedWallpaper = mLockWallpaperMap.get(mWallpaper.userId);
-                    if (lockedWallpaper == null) {
-                        which |= FLAG_LOCK;
-                    }
-                }
             }
             if (which != 0) {
                 notifyWallpaperColorsChangedOnDisplay(mWallpaper, which, displayId);
@@ -1489,9 +1333,7 @@
                                 wallpaper, null)) {
                             Slog.w(TAG, "Wallpaper " + wpService
                                     + " no longer available; reverting to default");
-                            int which = mIsLockscreenLiveWallpaperEnabled
-                                    ? wallpaper.mWhich : FLAG_SYSTEM;
-                            clearWallpaperLocked(which, wallpaper.userId, null);
+                            clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, null);
                         }
                     }
                 }
@@ -1565,7 +1407,6 @@
 
         boolean doPackagesChangedLocked(boolean doit, WallpaperData wallpaper) {
             boolean changed = false;
-            int which = mIsLockscreenLiveWallpaperEnabled ? wallpaper.mWhich : FLAG_SYSTEM;
             if (wallpaper.wallpaperComponent != null) {
                 int change = isPackageDisappearing(wallpaper.wallpaperComponent
                         .getPackageName());
@@ -1575,7 +1416,7 @@
                     if (doit) {
                         Slog.w(TAG, "Wallpaper uninstalled, removing: "
                                 + wallpaper.wallpaperComponent);
-                        clearWallpaperLocked(which, wallpaper.userId, null);
+                        clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, null);
                     }
                 }
             }
@@ -1596,7 +1437,7 @@
                 } catch (NameNotFoundException e) {
                     Slog.w(TAG, "Wallpaper component gone, removing: "
                             + wallpaper.wallpaperComponent);
-                    clearWallpaperLocked(which, wallpaper.userId, null);
+                    clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, null);
                 }
             }
             if (wallpaper.nextWallpaperComponent != null
@@ -1645,13 +1486,44 @@
         mWallpaperDisplayHelper = new WallpaperDisplayHelper(dm, mWindowManagerInternal);
         mWallpaperCropper = new WallpaperCropper(mWallpaperDisplayHelper);
         mActivityManager = mContext.getSystemService(ActivityManager.class);
+
+        if (mContext.getResources().getBoolean(
+                R.bool.config_pauseWallpaperRenderWhenStateChangeEnabled)) {
+            // Pause wallpaper rendering engine as soon as a performance impacted app is launched.
+            final String[] pauseRenderList = mContext.getResources().getStringArray(
+                    R.array.pause_wallpaper_render_when_state_change);
+            final IntArray pauseRenderUids = new IntArray();
+            for (String pauseRenderApp : pauseRenderList) {
+                try {
+                    int uid = mContext.getPackageManager().getApplicationInfo(
+                            pauseRenderApp, 0).uid;
+                    pauseRenderUids.add(uid);
+                } catch (Exception e) {
+                    Slog.e(TAG, e.toString());
+                }
+            }
+            if (pauseRenderUids.size() > 0) {
+                try {
+                    ActivityManager.getService().registerUidObserverForUids(new UidObserver() {
+                        @Override
+                        public void onUidStateChanged(int uid, int procState, long procStateSeq,
+                                int capability) {
+                            pauseOrResumeRenderingImmediately(
+                                    procState == ActivityManager.PROCESS_STATE_TOP);
+                        }
+                    }, ActivityManager.UID_OBSERVER_PROCSTATE,
+                            ActivityManager.PROCESS_STATE_TOP, "android",
+                            pauseRenderUids.toArray());
+                } catch (RemoteException e) {
+                    Slog.e(TAG, e.toString());
+                }
+            }
+        }
+
         mMonitor = new MyPackageMonitor();
         mColorsChangedListeners = new SparseArray<>();
         mWallpaperDataParser = new WallpaperDataParser(mContext, mWallpaperDisplayHelper,
                 mWallpaperCropper);
-
-        mIsLockscreenLiveWallpaperEnabled =
-                SystemProperties.getBoolean("persist.wm.debug.lockscreen_live_wallpaper", true);
         mIsMultiCropEnabled =
                 SystemProperties.getBoolean("persist.wm.debug.wallpaper_multi_crop", false);
         LocalServices.addService(WallpaperManagerInternal.class, new LocalService());
@@ -1718,8 +1590,7 @@
                 if (DEBUG) {
                     Slog.i(TAG, "Unable to regenerate crop; resetting");
                 }
-                int which = isLockscreenLiveWallpaperEnabled() ? wallpaper.mWhich : FLAG_SYSTEM;
-                clearWallpaperLocked(which, UserHandle.USER_SYSTEM, null);
+                clearWallpaperLocked(wallpaper.mWhich, UserHandle.USER_SYSTEM, false, null);
             }
         } else {
             if (DEBUG) {
@@ -1846,29 +1717,19 @@
     public void onUnlockUser(final int userId) {
         synchronized (mLock) {
             if (mCurrentUserId == userId) {
-                if (mIsLockscreenLiveWallpaperEnabled) {
-                    if (mHomeWallpaperWaitingForUnlock) {
-                        final WallpaperData systemWallpaper =
-                                getWallpaperSafeLocked(userId, FLAG_SYSTEM);
-                        switchWallpaper(systemWallpaper, null);
-                        // TODO(b/278261563): call notifyCallbacksLocked inside switchWallpaper
-                        notifyCallbacksLocked(systemWallpaper);
-                    }
-                    if (mLockWallpaperWaitingForUnlock) {
-                        final WallpaperData lockWallpaper =
-                                getWallpaperSafeLocked(userId, FLAG_LOCK);
-                        switchWallpaper(lockWallpaper, null);
-                        notifyCallbacksLocked(lockWallpaper);
-                    }
-                }
-
-                if (mWaitingForUnlock && !mIsLockscreenLiveWallpaperEnabled) {
-                    // the desired wallpaper is not direct-boot aware, load it now
+                if (mHomeWallpaperWaitingForUnlock) {
                     final WallpaperData systemWallpaper =
                             getWallpaperSafeLocked(userId, FLAG_SYSTEM);
                     switchWallpaper(systemWallpaper, null);
+                    // TODO(b/278261563): call notifyCallbacksLocked inside switchWallpaper
                     notifyCallbacksLocked(systemWallpaper);
                 }
+                if (mLockWallpaperWaitingForUnlock) {
+                    final WallpaperData lockWallpaper =
+                            getWallpaperSafeLocked(userId, FLAG_LOCK);
+                    switchWallpaper(lockWallpaper, null);
+                    notifyCallbacksLocked(lockWallpaper);
+                }
 
                 // Make sure that the SELinux labeling of all the relevant files is correct.
                 // This corrects for mislabeling bugs that might have arisen from move-to
@@ -1917,21 +1778,15 @@
                 }
                 mCurrentUserId = userId;
                 systemWallpaper = getWallpaperSafeLocked(userId, FLAG_SYSTEM);
-
-                if (mIsLockscreenLiveWallpaperEnabled) {
-                    lockWallpaper = systemWallpaper.mWhich == (FLAG_LOCK | FLAG_SYSTEM)
-                            ? systemWallpaper : getWallpaperSafeLocked(userId, FLAG_LOCK);
-                } else {
-                    final WallpaperData tmpLockWallpaper = mLockWallpaperMap.get(userId);
-                    lockWallpaper = tmpLockWallpaper == null ? systemWallpaper : tmpLockWallpaper;
-                }
+                lockWallpaper = systemWallpaper.mWhich == (FLAG_LOCK | FLAG_SYSTEM)
+                        ? systemWallpaper : getWallpaperSafeLocked(userId, FLAG_LOCK);
 
                 // Not started watching yet, in case wallpaper data was loaded for other reasons.
                 if (systemWallpaper.wallpaperObserver == null) {
                     systemWallpaper.wallpaperObserver = new WallpaperObserver(systemWallpaper);
                     systemWallpaper.wallpaperObserver.startWatching();
                 }
-                if (mIsLockscreenLiveWallpaperEnabled && lockWallpaper != systemWallpaper)  {
+                if (lockWallpaper != systemWallpaper)  {
                     switchWallpaper(lockWallpaper, null);
                 }
                 switchWallpaper(systemWallpaper, reply);
@@ -1951,11 +1806,8 @@
 
     void switchWallpaper(WallpaperData wallpaper, IRemoteCallback reply) {
         synchronized (mLock) {
-            mWaitingForUnlock = false;
-            if (mIsLockscreenLiveWallpaperEnabled) {
-                if ((wallpaper.mWhich & FLAG_SYSTEM) != 0) mHomeWallpaperWaitingForUnlock = false;
-                if ((wallpaper.mWhich & FLAG_LOCK) != 0) mLockWallpaperWaitingForUnlock = false;
-            }
+            if ((wallpaper.mWhich & FLAG_SYSTEM) != 0) mHomeWallpaperWaitingForUnlock = false;
+            if ((wallpaper.mWhich & FLAG_LOCK) != 0) mLockWallpaperWaitingForUnlock = false;
 
             final ComponentName cname = wallpaper.wallpaperComponent != null ?
                     wallpaper.wallpaperComponent : wallpaper.nextWallpaperComponent;
@@ -1969,37 +1821,19 @@
                 } catch (RemoteException e) {
                     Slog.w(TAG, "Failure starting previous wallpaper; clearing", e);
                 }
-
-                if (mIsLockscreenLiveWallpaperEnabled) {
-                    onSwitchWallpaperFailLocked(wallpaper, reply, si);
-                    return;
-                }
-
-                if (si == null) {
-                    clearWallpaperLocked(FLAG_SYSTEM, wallpaper.userId, reply);
-                } else {
-                    Slog.w(TAG, "Wallpaper isn't direct boot aware; using fallback until unlocked");
-                    // We might end up persisting the current wallpaper data
-                    // while locked, so pretend like the component was actually
-                    // bound into place
-                    wallpaper.wallpaperComponent = wallpaper.nextWallpaperComponent;
-                    final WallpaperData fallback = new WallpaperData(wallpaper.userId, FLAG_LOCK);
-                    bindWallpaperComponentLocked(mImageWallpaper, true, false, fallback, reply);
-                    mWaitingForUnlock = true;
-                }
+                onSwitchWallpaperFailLocked(wallpaper, reply, si);
             }
         }
     }
 
     /**
      * Fallback method if a wallpaper fails to load on boot or after a user switch.
-     * Only called if mIsLockscreenLiveWallpaperEnabled is true.
      */
     private void onSwitchWallpaperFailLocked(
             WallpaperData wallpaper, IRemoteCallback reply, ServiceInfo serviceInfo) {
 
         if (serviceInfo == null) {
-            clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, reply);
+            clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, reply);
             return;
         }
         Slog.w(TAG, "Wallpaper isn't direct boot aware; using fallback until unlocked");
@@ -2030,12 +1864,8 @@
 
         WallpaperData data = null;
         synchronized (mLock) {
-            if (mIsLockscreenLiveWallpaperEnabled) {
-                boolean fromForeground = isFromForegroundApp(callingPackage);
-                clearWallpaperLocked(which, userId, fromForeground, null);
-            } else {
-                clearWallpaperLocked(which, userId, null);
-            }
+            boolean fromForeground = isFromForegroundApp(callingPackage);
+            clearWallpaperLocked(which, userId, fromForeground, null);
 
             if (which == FLAG_LOCK) {
                 data = mLockWallpaperMap.get(userId);
@@ -2116,91 +1946,6 @@
         }
     }
 
-    // TODO(b/266818039) remove
-    private void clearWallpaperLocked(int which, int userId, IRemoteCallback reply) {
-
-        if (mIsLockscreenLiveWallpaperEnabled) {
-            clearWallpaperLocked(which, userId, false, reply);
-            return;
-        }
-
-        if (which != FLAG_SYSTEM && which != FLAG_LOCK) {
-            throw new IllegalArgumentException("Must specify exactly one kind of wallpaper to clear");
-        }
-
-        WallpaperData wallpaper = null;
-        if (which == FLAG_LOCK) {
-            wallpaper = mLockWallpaperMap.get(userId);
-            if (wallpaper == null) {
-                // It's already gone; we're done.
-                if (DEBUG) {
-                    Slog.i(TAG, "Lock wallpaper already cleared");
-                }
-                return;
-            }
-        } else {
-            wallpaper = mWallpaperMap.get(userId);
-            if (wallpaper == null) {
-                // Might need to bring it in the first time to establish our rewrite
-                loadSettingsLocked(userId, false, FLAG_SYSTEM);
-                wallpaper = mWallpaperMap.get(userId);
-            }
-        }
-        if (wallpaper == null) {
-            return;
-        }
-
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            if (clearWallpaperBitmaps(wallpaper)) {
-                if (which == FLAG_LOCK) {
-                    mLockWallpaperMap.remove(userId);
-                    final IWallpaperManagerCallback cb = mKeyguardListener;
-                    if (cb != null) {
-                        if (DEBUG) {
-                            Slog.i(TAG, "Notifying keyguard of lock wallpaper clear");
-                        }
-                        try {
-                            cb.onWallpaperChanged();
-                        } catch (RemoteException e) {
-                            Slog.w(TAG, "Failed to notify keyguard after wallpaper clear", e);
-                        }
-                    }
-                    saveSettingsLocked(userId);
-                    return;
-                }
-            }
-
-            RuntimeException e = null;
-            try {
-                wallpaper.primaryColors = null;
-                wallpaper.imageWallpaperPending = false;
-                if (userId != mCurrentUserId) return;
-                if (bindWallpaperComponentLocked(null, true, false, wallpaper, reply)) {
-                    return;
-                }
-            } catch (IllegalArgumentException e1) {
-                e = e1;
-            }
-
-            // This can happen if the default wallpaper component doesn't
-            // exist.  This should be a system configuration problem, but
-            // let's not let it crash the system and just live with no
-            // wallpaper.
-            Slog.e(TAG, "Default wallpaper component not found!", e);
-            clearWallpaperComponentLocked(wallpaper);
-            if (reply != null) {
-                try {
-                    reply.sendResult(null);
-                } catch (RemoteException e1) {
-                    Slog.w(TAG, "Failed to notify callback after wallpaper clear", e1);
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
     private boolean hasCrossUserPermission() {
         final int interactPermission =
                 mContext.checkCallingPermission(INTERACT_ACROSS_USERS_FULL);
@@ -2578,45 +2323,20 @@
      * @param animationDuration Duration of the animation, or 0 when immediate.
      */
     public void setInAmbientMode(boolean inAmbientMode, long animationDuration) {
-        if (mIsLockscreenLiveWallpaperEnabled) {
-            List<IWallpaperEngine> engines = new ArrayList<>();
-            synchronized (mLock) {
-                mInAmbientMode = inAmbientMode;
-                for (WallpaperData data : getActiveWallpapers()) {
-                    if (data.connection.mInfo == null
-                            || data.connection.mInfo.supportsAmbientMode()) {
-                        // TODO(multi-display) Extends this method with specific display.
-                        IWallpaperEngine engine = data.connection
-                                .getDisplayConnectorOrCreate(DEFAULT_DISPLAY).mEngine;
-                        if (engine != null) engines.add(engine);
-                    }
-                }
-            }
-            for (IWallpaperEngine engine : engines) {
-                try {
-                    engine.setInAmbientMode(inAmbientMode, animationDuration);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "Failed to set ambient mode", e);
-                }
-            }
-            return;
-        }
-
-        final IWallpaperEngine engine;
+        List<IWallpaperEngine> engines = new ArrayList<>();
         synchronized (mLock) {
             mInAmbientMode = inAmbientMode;
-            final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
-            // The wallpaper info is null for image wallpaper, also use the engine in this case.
-            if (data != null && data.connection != null && (data.connection.mInfo == null
-                    || data.connection.mInfo.supportsAmbientMode())) {
-                // TODO(multi-display) Extends this method with specific display.
-                engine = data.connection.getDisplayConnectorOrCreate(DEFAULT_DISPLAY).mEngine;
-            } else {
-                engine = null;
+            for (WallpaperData data : getActiveWallpapers()) {
+                if (data.connection.mInfo == null
+                        || data.connection.mInfo.supportsAmbientMode()) {
+                    // TODO(multi-display) Extends this method with specific display.
+                    IWallpaperEngine engine = data.connection
+                            .getDisplayConnectorOrCreate(DEFAULT_DISPLAY).mEngine;
+                    if (engine != null) engines.add(engine);
+                }
             }
         }
-
-        if (engine != null) {
+        for (IWallpaperEngine engine : engines) {
             try {
                 engine.setInAmbientMode(inAmbientMode, animationDuration);
             } catch (RemoteException e) {
@@ -2625,40 +2345,49 @@
         }
     }
 
+    private void pauseOrResumeRenderingImmediately(boolean pause) {
+        synchronized (mLock) {
+            for (WallpaperData data : getActiveWallpapers()) {
+                if (data.connection.mInfo == null) {
+                    continue;
+                }
+                if (pause || LocalServices.getService(ActivityTaskManagerInternal.class)
+                        .isUidForeground(data.connection.mInfo.getServiceInfo()
+                                .applicationInfo.uid)) {
+                    if (data.connection.containsDisplay(
+                            mWindowManagerInternal.getTopFocusedDisplayId())) {
+                        data.connection.forEachDisplayConnector(displayConnector -> {
+                            if (displayConnector.mEngine != null) {
+                                try {
+                                    displayConnector.mEngine.setVisibility(!pause);
+                                } catch (RemoteException e) {
+                                    Slog.w(TAG, "Failed to set visibility", e);
+                                }
+                            }
+                        });
+                    }
+                }
+            }
+        }
+    }
+
     /**
      * Propagate a wake event to the wallpaper engine(s).
      */
     public void notifyWakingUp(int x, int y, @NonNull Bundle extras) {
         checkCallerIsSystemOrSystemUi();
         synchronized (mLock) {
-            if (mIsLockscreenLiveWallpaperEnabled) {
-                for (WallpaperData data : getActiveWallpapers()) {
-                    data.connection.forEachDisplayConnector(displayConnector -> {
-                        if (displayConnector.mEngine != null) {
-                            try {
-                                displayConnector.mEngine.dispatchWallpaperCommand(
-                                        WallpaperManager.COMMAND_WAKING_UP, x, y, -1, extras);
-                            } catch (RemoteException e) {
-                                Slog.w(TAG, "Failed to dispatch COMMAND_WAKING_UP", e);
-                            }
+            for (WallpaperData data : getActiveWallpapers()) {
+                data.connection.forEachDisplayConnector(displayConnector -> {
+                    if (displayConnector.mEngine != null) {
+                        try {
+                            displayConnector.mEngine.dispatchWallpaperCommand(
+                                    WallpaperManager.COMMAND_WAKING_UP, x, y, -1, extras);
+                        } catch (RemoteException e) {
+                            Slog.w(TAG, "Failed to dispatch COMMAND_WAKING_UP", e);
                         }
-                    });
-                }
-                return;
-            }
-            final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
-            if (data != null && data.connection != null) {
-                data.connection.forEachDisplayConnector(
-                        displayConnector -> {
-                            if (displayConnector.mEngine != null) {
-                                try {
-                                    displayConnector.mEngine.dispatchWallpaperCommand(
-                                            WallpaperManager.COMMAND_WAKING_UP, x, y, -1, extras);
-                                } catch (RemoteException e) {
-                                    Slog.w(TAG, "Failed to dispatch COMMAND_WAKING_UP", e);
-                                }
-                            }
-                        });
+                    }
+                });
             }
         }
     }
@@ -2669,36 +2398,18 @@
     public void notifyGoingToSleep(int x, int y, @NonNull Bundle extras) {
         checkCallerIsSystemOrSystemUi();
         synchronized (mLock) {
-            if (mIsLockscreenLiveWallpaperEnabled) {
-                for (WallpaperData data : getActiveWallpapers()) {
-                    data.connection.forEachDisplayConnector(displayConnector -> {
-                        if (displayConnector.mEngine != null) {
-                            try {
-                                displayConnector.mEngine.dispatchWallpaperCommand(
-                                        WallpaperManager.COMMAND_GOING_TO_SLEEP, x, y, -1,
-                                        extras);
-                            } catch (RemoteException e) {
-                                Slog.w(TAG, "Failed to dispatch COMMAND_GOING_TO_SLEEP", e);
-                            }
+            for (WallpaperData data : getActiveWallpapers()) {
+                data.connection.forEachDisplayConnector(displayConnector -> {
+                    if (displayConnector.mEngine != null) {
+                        try {
+                            displayConnector.mEngine.dispatchWallpaperCommand(
+                                    WallpaperManager.COMMAND_GOING_TO_SLEEP, x, y, -1,
+                                    extras);
+                        } catch (RemoteException e) {
+                            Slog.w(TAG, "Failed to dispatch COMMAND_GOING_TO_SLEEP", e);
                         }
-                    });
-                }
-                return;
-            }
-            final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
-            if (data != null && data.connection != null) {
-                data.connection.forEachDisplayConnector(
-                        displayConnector -> {
-                            if (displayConnector.mEngine != null) {
-                                try {
-                                    displayConnector.mEngine.dispatchWallpaperCommand(
-                                            WallpaperManager.COMMAND_GOING_TO_SLEEP, x, y, -1,
-                                            extras);
-                                } catch (RemoteException e) {
-                                    Slog.w(TAG, "Failed to dispatch COMMAND_GOING_TO_SLEEP", e);
-                                }
-                            }
-                        });
+                    }
+                });
             }
         }
     }
@@ -2708,35 +2419,18 @@
      */
     private void notifyScreenTurnedOn(int displayId) {
         synchronized (mLock) {
-            if (mIsLockscreenLiveWallpaperEnabled) {
-                for (WallpaperData data : getActiveWallpapers()) {
-                    if (data.connection.containsDisplay(displayId)) {
-                        final IWallpaperEngine engine = data.connection
-                                .getDisplayConnectorOrCreate(displayId).mEngine;
-                        if (engine != null) {
-                            try {
-                                engine.onScreenTurnedOn();
-                            } catch (RemoteException e) {
-                                Slog.w(TAG, "Failed to notify that the screen turned on", e);
-                            }
+            for (WallpaperData data : getActiveWallpapers()) {
+                if (data.connection.containsDisplay(displayId)) {
+                    final IWallpaperEngine engine = data.connection
+                            .getDisplayConnectorOrCreate(displayId).mEngine;
+                    if (engine != null) {
+                        try {
+                            engine.onScreenTurnedOn();
+                        } catch (RemoteException e) {
+                            Slog.w(TAG, "Failed to notify that the screen turned on", e);
                         }
                     }
                 }
-                return;
-            }
-            final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
-            if (data != null
-                    && data.connection != null
-                    && data.connection.containsDisplay(displayId)) {
-                final IWallpaperEngine engine = data.connection
-                        .getDisplayConnectorOrCreate(displayId).mEngine;
-                if (engine != null) {
-                    try {
-                        engine.onScreenTurnedOn();
-                    } catch (RemoteException e) {
-                        Slog.w(TAG, "Failed to notify that the screen turned on", e);
-                    }
-                }
             }
         }
     }
@@ -2746,35 +2440,18 @@
      */
     private void notifyScreenTurningOn(int displayId) {
         synchronized (mLock) {
-            if (mIsLockscreenLiveWallpaperEnabled) {
-                for (WallpaperData data : getActiveWallpapers()) {
-                    if (data.connection.containsDisplay(displayId)) {
-                        final IWallpaperEngine engine = data.connection
-                                .getDisplayConnectorOrCreate(displayId).mEngine;
-                        if (engine != null) {
-                            try {
-                                engine.onScreenTurningOn();
-                            } catch (RemoteException e) {
-                                Slog.w(TAG, "Failed to notify that the screen is turning on", e);
-                            }
+            for (WallpaperData data : getActiveWallpapers()) {
+                if (data.connection.containsDisplay(displayId)) {
+                    final IWallpaperEngine engine = data.connection
+                            .getDisplayConnectorOrCreate(displayId).mEngine;
+                    if (engine != null) {
+                        try {
+                            engine.onScreenTurningOn();
+                        } catch (RemoteException e) {
+                            Slog.w(TAG, "Failed to notify that the screen is turning on", e);
                         }
                     }
                 }
-                return;
-            }
-            final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
-            if (data != null
-                    && data.connection != null
-                    && data.connection.containsDisplay(displayId)) {
-                final IWallpaperEngine engine = data.connection
-                        .getDisplayConnectorOrCreate(displayId).mEngine;
-                if (engine != null) {
-                    try {
-                        engine.onScreenTurningOn();
-                    } catch (RemoteException e) {
-                        Slog.w(TAG, "Failed to notify that the screen is turning on", e);
-                    }
-                }
             }
         }
     }
@@ -2784,25 +2461,7 @@
      */
     private void notifyKeyguardGoingAway() {
         synchronized (mLock) {
-            if (mIsLockscreenLiveWallpaperEnabled) {
-                for (WallpaperData data : getActiveWallpapers()) {
-                    data.connection.forEachDisplayConnector(displayConnector -> {
-                        if (displayConnector.mEngine != null) {
-                            try {
-                                displayConnector.mEngine.dispatchWallpaperCommand(
-                                        WallpaperManager.COMMAND_KEYGUARD_GOING_AWAY,
-                                        -1, -1, -1, new Bundle());
-                            } catch (RemoteException e) {
-                                Slog.w(TAG, "Failed to notify that the keyguard is going away", e);
-                            }
-                        }
-                    });
-                }
-                return;
-            }
-
-            final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
-            if (data != null && data.connection != null) {
+            for (WallpaperData data : getActiveWallpapers()) {
                 data.connection.forEachDisplayConnector(displayConnector -> {
                     if (displayConnector.mEngine != null) {
                         try {
@@ -2818,15 +2477,6 @@
         }
     }
 
-    @Override
-    public boolean setLockWallpaperCallback(IWallpaperManagerCallback cb) {
-        checkPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW);
-        synchronized (mLock) {
-            mKeyguardListener = cb;
-        }
-        return true;
-    }
-
     private WallpaperData[] getActiveWallpapers() {
         WallpaperData systemWallpaper = mWallpaperMap.get(mCurrentUserId);
         WallpaperData lockWallpaper = mLockWallpaperMap.get(mCurrentUserId);
@@ -2838,12 +2488,11 @@
                 : new WallpaperData[0];
     }
 
-    // TODO(b/266818039) remove
     private WallpaperData[] getWallpapers() {
         WallpaperData systemWallpaper = mWallpaperMap.get(mCurrentUserId);
         WallpaperData lockWallpaper = mLockWallpaperMap.get(mCurrentUserId);
         boolean systemValid = systemWallpaper != null;
-        boolean lockValid = lockWallpaper != null && isLockscreenLiveWallpaperEnabled();
+        boolean lockValid = lockWallpaper != null;
         return systemValid && lockValid ? new WallpaperData[]{systemWallpaper, lockWallpaper}
                 : systemValid ? new WallpaperData[]{systemWallpaper}
                 : lockValid ? new WallpaperData[]{lockWallpaper}
@@ -2977,54 +2626,29 @@
                     lockWallpaper.mWallpaperDimAmount = maxDimAmount;
                 }
 
-                if (mIsLockscreenLiveWallpaperEnabled) {
-                    boolean changed = false;
-                    for (WallpaperData wp : getActiveWallpapers()) {
-                        if (wp != null && wp.connection != null) {
-                            wp.connection.forEachDisplayConnector(connector -> {
-                                if (connector.mEngine != null) {
-                                    try {
-                                        connector.mEngine.applyDimming(maxDimAmount);
-                                    } catch (RemoteException e) {
-                                        Slog.w(TAG, "Can't apply dimming on wallpaper display "
-                                                        + "connector", e);
-                                    }
-                                }
-                            });
-                            // Need to extract colors again to re-calculate dark hints after
-                            // applying dimming.
-                            wp.mIsColorExtractedFromDim = true;
-                            pendingColorExtraction.add(wp);
-                            changed = true;
-                        }
-                    }
-                    if (changed) {
-                        saveSettingsLocked(wallpaper.userId);
-                    }
-                } else {
-                    if (wallpaper.connection != null) {
-                        wallpaper.connection.forEachDisplayConnector(connector -> {
+                boolean changed = false;
+                for (WallpaperData wp : getActiveWallpapers()) {
+                    if (wp != null && wp.connection != null) {
+                        wp.connection.forEachDisplayConnector(connector -> {
                             if (connector.mEngine != null) {
                                 try {
                                     connector.mEngine.applyDimming(maxDimAmount);
                                 } catch (RemoteException e) {
-                                    Slog.w(TAG,
-                                            "Can't apply dimming on wallpaper display connector",
-                                            e);
+                                    Slog.w(TAG, "Can't apply dimming on wallpaper display "
+                                                    + "connector", e);
                                 }
                             }
                         });
                         // Need to extract colors again to re-calculate dark hints after
                         // applying dimming.
-                        wallpaper.mIsColorExtractedFromDim = true;
-                        notifyWallpaperColorsChanged(wallpaper, FLAG_SYSTEM);
-                        if (lockWallpaper != null) {
-                            lockWallpaper.mIsColorExtractedFromDim = true;
-                            notifyWallpaperColorsChanged(lockWallpaper, FLAG_LOCK);
-                        }
-                        saveSettingsLocked(wallpaper.userId);
+                        wp.mIsColorExtractedFromDim = true;
+                        pendingColorExtraction.add(wp);
+                        changed = true;
                     }
                 }
+                if (changed) {
+                    saveSettingsLocked(wallpaper.userId);
+                }
             }
             for (WallpaperData wp: pendingColorExtraction) {
                 notifyWallpaperColorsChanged(wp, wp.mWhich);
@@ -3180,10 +2804,7 @@
             if (which == FLAG_SYSTEM && systemIsStatic && systemIsBoth) {
                 Slog.i(TAG, "Migrating current wallpaper to be lock-only before"
                         + " updating system wallpaper");
-                if (!migrateStaticSystemToLockWallpaperLocked(userId)
-                        && !isLockscreenLiveWallpaperEnabled()) {
-                    which |= FLAG_LOCK;
-                }
+                migrateStaticSystemToLockWallpaperLocked(userId);
             }
 
             wallpaper = getWallpaperSafeLocked(userId, which);
@@ -3211,13 +2832,13 @@
         }
     }
 
-    private boolean migrateStaticSystemToLockWallpaperLocked(int userId) {
+    private void migrateStaticSystemToLockWallpaperLocked(int userId) {
         WallpaperData sysWP = mWallpaperMap.get(userId);
         if (sysWP == null) {
             if (DEBUG) {
                 Slog.i(TAG, "No system wallpaper?  Not tracking for lock-only");
             }
-            return true;
+            return;
         }
 
         // We know a-priori that there is no lock-only wallpaper currently
@@ -3231,25 +2852,21 @@
 
         // Migrate the bitmap files outright; no need to copy
         try {
-            if (!mIsLockscreenLiveWallpaperEnabled || sysWP.getWallpaperFile().exists()) {
+            if (sysWP.getWallpaperFile().exists()) {
                 Os.rename(sysWP.getWallpaperFile().getAbsolutePath(),
                         lockWP.getWallpaperFile().getAbsolutePath());
             }
-            if (!mIsLockscreenLiveWallpaperEnabled || sysWP.getCropFile().exists()) {
+            if (sysWP.getCropFile().exists()) {
                 Os.rename(sysWP.getCropFile().getAbsolutePath(),
                         lockWP.getCropFile().getAbsolutePath());
             }
             mLockWallpaperMap.put(userId, lockWP);
-            if (mIsLockscreenLiveWallpaperEnabled) {
-                SELinux.restorecon(lockWP.getWallpaperFile());
-                mLastLockWallpaper = lockWP;
-            }
-            return true;
+            SELinux.restorecon(lockWP.getWallpaperFile());
+            mLastLockWallpaper = lockWP;
         } catch (ErrnoException e) {
             // can happen when migrating default wallpaper (which is not stored in wallpaperFile)
             Slog.w(TAG, "Couldn't migrate system wallpaper: " + e.getMessage());
             clearWallpaperBitmaps(lockWP);
-            return false;
         }
     }
 
@@ -3306,13 +2923,8 @@
     @VisibleForTesting
     boolean setWallpaperComponent(ComponentName name, String callingPackage,
             @SetWallpaperFlags int which, int userId) {
-        if (mIsLockscreenLiveWallpaperEnabled) {
-            boolean fromForeground = isFromForegroundApp(callingPackage);
-            return setWallpaperComponentInternal(name, which, userId, false, fromForeground, null);
-        } else {
-            setWallpaperComponentInternalLegacy(name, callingPackage, which, userId);
-            return true;
-        }
+        boolean fromForeground = isFromForegroundApp(callingPackage);
+        return setWallpaperComponentInternal(name, which, userId, false, fromForeground, null);
     }
 
     private boolean setWallpaperComponentInternal(ComponentName name,  @SetWallpaperFlags int which,
@@ -3425,87 +3037,6 @@
         return bindSuccess;
     }
 
-    // TODO(b/266818039) Remove this method
-    private void setWallpaperComponentInternalLegacy(ComponentName name, String callingPackage,
-            @SetWallpaperFlags int which, int userId) {
-        userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,
-                false /* all */, true /* full */, "changing live wallpaper", null /* pkg */);
-        checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);
-
-        int legacyWhich = FLAG_SYSTEM;
-        boolean shouldNotifyColors = false;
-        WallpaperData wallpaper;
-
-        synchronized (mLock) {
-            Slog.v(TAG, "setWallpaperComponentLegacy name=" + name + ", which=" + which);
-            wallpaper = mWallpaperMap.get(userId);
-            if (wallpaper == null) {
-                throw new IllegalStateException("Wallpaper not yet initialized for user " + userId);
-            }
-            final long ident = Binder.clearCallingIdentity();
-
-            // Live wallpapers can't be specified for keyguard.  If we're using a static
-            // system+lock image currently, migrate the system wallpaper to be a lock-only
-            // image as part of making a different live component active as the system
-            // wallpaper.
-            if (mImageWallpaper.equals(wallpaper.wallpaperComponent)) {
-                if (mLockWallpaperMap.get(userId) == null) {
-                    // We're using the static imagery and there is no lock-specific image in place,
-                    // therefore it's a shared system+lock image that we need to migrate.
-                    Slog.i(TAG, "Migrating current wallpaper to be lock-only before"
-                            + "updating system wallpaper");
-                    if (!migrateStaticSystemToLockWallpaperLocked(userId)) {
-                        which |= FLAG_LOCK;
-                    }
-                }
-            }
-
-            // New live wallpaper is also a lock wallpaper if nothing is set
-            if (mLockWallpaperMap.get(userId) == null) {
-                legacyWhich |= FLAG_LOCK;
-            }
-
-            try {
-                wallpaper.imageWallpaperPending = false;
-                wallpaper.mWhich = which;
-                wallpaper.fromForegroundApp = isFromForegroundApp(callingPackage);
-                boolean same = changingToSame(name, wallpaper);
-
-                // force rebind when reapplying a system-only wallpaper to system+lock
-                boolean forceRebind = same && mLockWallpaperMap.get(userId) != null
-                        && which == (FLAG_SYSTEM | FLAG_LOCK);
-                if (bindWallpaperComponentLocked(name, forceRebind, true, wallpaper, null)) {
-                    if (!same) {
-                        wallpaper.primaryColors = null;
-                    } else {
-                        if (wallpaper.connection != null) {
-                            wallpaper.connection.forEachDisplayConnector(displayConnector -> {
-                                try {
-                                    if (displayConnector.mEngine != null) {
-                                        displayConnector.mEngine.dispatchWallpaperCommand(
-                                                COMMAND_REAPPLY, 0, 0, 0, null);
-                                    }
-                                } catch (RemoteException e) {
-                                    Slog.w(TAG, "Error sending apply message to wallpaper", e);
-                                }
-                            });
-                        }
-                    }
-                    wallpaper.wallpaperId = makeWallpaperIdLocked();
-                    notifyCallbacksLocked(wallpaper);
-                    shouldNotifyColors = true;
-                }
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-
-        if (shouldNotifyColors) {
-            notifyWallpaperColorsChanged(wallpaper, legacyWhich);
-            notifyWallpaperColorsChanged(mFallbackWallpaper, FLAG_SYSTEM);
-        }
-    }
-
     /**
      * Determines if the given component name is the default component. Note: a null name can be
      * used to represent the default component.
@@ -3677,21 +3208,11 @@
                 Slog.w(TAG, msg);
                 return false;
             }
-            if (mIsLockscreenLiveWallpaperEnabled) {
-                maybeDetachLastWallpapers(wallpaper);
-            } else if (wallpaper.userId == mCurrentUserId && mLastWallpaper != null
-                    && !wallpaper.equals(mFallbackWallpaper)) {
-                detachWallpaperLocked(mLastWallpaper);
-            }
+            maybeDetachLastWallpapers(wallpaper);
             wallpaper.wallpaperComponent = componentName;
             wallpaper.connection = newConn;
             newConn.mReply = reply;
-            if (mIsLockscreenLiveWallpaperEnabled) {
-                updateCurrentWallpapers(wallpaper);
-            } else if (wallpaper.userId == mCurrentUserId && !wallpaper.equals(
-                    mFallbackWallpaper)) {
-                mLastWallpaper = wallpaper;
-            }
+            updateCurrentWallpapers(wallpaper);
             updateFallbackConnection();
         } catch (RemoteException e) {
             String msg = "Remote exception for " + componentName + "\n" + e;
@@ -3707,7 +3228,6 @@
     }
 
     // Updates tracking of the currently bound wallpapers.
-    // Assumes isLockscreenLiveWallpaperEnabled is true.
     private void updateCurrentWallpapers(WallpaperData newWallpaper) {
         if (newWallpaper.userId != mCurrentUserId || newWallpaper.equals(mFallbackWallpaper)) {
             return;
@@ -3721,8 +3241,7 @@
         }
     }
 
-    // Detaches previously bound wallpapers if no longer in use. Assumes
-    // isLockscreenLiveWallpaperEnabled is true.
+    // Detaches previously bound wallpapers if no longer in use.
     private void maybeDetachLastWallpapers(WallpaperData newWallpaper) {
         if (newWallpaper.userId != mCurrentUserId || newWallpaper.equals(mFallbackWallpaper)) {
             return;
@@ -3915,11 +3434,6 @@
     }
 
     @Override
-    public boolean isLockscreenLiveWallpaperEnabled() {
-        return mIsLockscreenLiveWallpaperEnabled;
-    }
-
-    @Override
     public boolean isMultiCropEnabled() {
         return mIsMultiCropEnabled;
     }
@@ -3982,7 +3496,9 @@
         if (wallpaper == null) {
             // common case, this is the first lookup post-boot of the system or
             // unified lock, so we bring up the saved state lazily now and recheck.
-            int whichLoad = (which == FLAG_LOCK) ? FLAG_LOCK : FLAG_SYSTEM;
+            // if we're loading the system wallpaper for the first time, also load the lock
+            // wallpaper to determine if the system wallpaper is system+lock or system only.
+            int whichLoad = (which == FLAG_LOCK) ? FLAG_LOCK : FLAG_SYSTEM | FLAG_LOCK;
             loadSettingsLocked(userId, false, whichLoad);
             wallpaper = whichSet.get(userId);
             if (wallpaper == null) {
@@ -4006,13 +3522,12 @@
 
     private void loadSettingsLocked(int userId, boolean keepDimensionHints, int which) {
         initializeFallbackWallpaper();
-        WallpaperData wallpaperData = mWallpaperMap.get(userId);
-        WallpaperData lockWallpaperData = mLockWallpaperMap.get(userId);
+        boolean restoreFromOld = !mWallpaperMap.contains(userId);
         WallpaperDataParser.WallpaperLoadingResult result = mWallpaperDataParser.loadSettingsLocked(
-                userId, keepDimensionHints, wallpaperData, lockWallpaperData, which);
+                userId, keepDimensionHints, restoreFromOld, which);
 
-        boolean updateSystem = !mIsLockscreenLiveWallpaperEnabled || (which & FLAG_SYSTEM) != 0;
-        boolean updateLock = !mIsLockscreenLiveWallpaperEnabled || (which & FLAG_LOCK) != 0;
+        boolean updateSystem = (which & FLAG_SYSTEM) != 0;
+        boolean updateLock = (which & FLAG_LOCK) != 0;
 
         if (updateSystem) mWallpaperMap.put(userId, result.getSystemWallpaperData());
         if (updateLock) {
@@ -4175,8 +3690,6 @@
             if (mFallbackWallpaper != null) {
                 dumpWallpaper(mFallbackWallpaper, pw);
             }
-            pw.print("mIsLockscreenLiveWallpaperEnabled=");
-            pw.println(mIsLockscreenLiveWallpaperEnabled);
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index a01113b..9fa5ed2 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4030,6 +4030,9 @@
         if (mAppStopped) {
             abortAndClearOptionsAnimation();
         }
+        if (mDisplayContent != null) {
+            mDisplayContent.mUnknownAppVisibilityController.appRemovedOrHidden(this);
+        }
     }
 
     boolean isFinishing() {
@@ -5388,12 +5391,6 @@
         mLastDeferHidingClient = deferHidingClient;
 
         if (!visible) {
-            // If this activity is about to finish/stopped and now becomes invisible, remove it
-            // from the unknownApp list in case the activity does not want to draw anything, which
-            // keep the user waiting for the next transition to start.
-            if (finishing || isState(STOPPED)) {
-                displayContent.mUnknownAppVisibilityController.appRemovedOrHidden(this);
-            }
             // Because starting window was transferred, this activity may be a trampoline which has
             // been occluded by next activity. If it has added windows, set client visibility
             // immediately to avoid the client getting RELAYOUT_RES_FIRST_TIME from relayout and
@@ -5837,6 +5834,9 @@
                 break;
             case STOPPED:
                 mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_STOPPED);
+                if (mDisplayContent != null) {
+                    mDisplayContent.mUnknownAppVisibilityController.appRemovedOrHidden(this);
+                }
                 break;
             case DESTROYED:
                 if (app != null && (mVisible || mVisibleRequested)) {
@@ -6517,7 +6517,6 @@
         }
         // Reset the last saved PiP snap fraction on app stop.
         mDisplayContent.mPinnedTaskController.onActivityHidden(mActivityComponent);
-        mDisplayContent.mUnknownAppVisibilityController.appRemovedOrHidden(this);
         if (isClientVisible()) {
             // Though this is usually unlikely to happen, still make sure the client is invisible.
             setClientVisible(false);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index de335d3..c021785 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3002,7 +3002,7 @@
                 || mLastResumedActivity == null) {
             return;
         }
-        var userInfo = mUserManager.getUserInfo(mLastResumedActivity.mUserId);
+        var userInfo = getUserManager().getUserInfo(mLastResumedActivity.mUserId);
         if (userInfo == null || !userInfo.isManagedProfile()) {
             return;
         }
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index 4444709..c79a8b6 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -378,8 +378,23 @@
                 if (!wc.isSyncFinished(this)) {
                     allFinished = false;
                     Slog.i(TAG, "Unfinished container: " + wc);
+                    wc.forAllActivities(a -> {
+                        if (a.isVisibleRequested()) {
+                            if (a.isRelaunching()) {
+                                Slog.i(TAG, "  " + a + " is relaunching");
+                            }
+                            a.forAllWindows(w -> {
+                                Slog.i(TAG, "  " + w + " " + w.mWinAnimator.drawStateToString());
+                            }, true /* traverseTopToBottom */);
+                        } else if (a.mDisplayContent != null && !a.mDisplayContent
+                                .mUnknownAppVisibilityController.allResolved()) {
+                            Slog.i(TAG, "  UnknownAppVisibility: " + a.mDisplayContent
+                                    .mUnknownAppVisibilityController.getDebugMessage());
+                        }
+                    });
                 }
             }
+
             for (int i = mDependencies.size() - 1; i >= 0; --i) {
                 allFinished = false;
                 Slog.i(TAG, "Unfinished dependency: " + mDependencies.get(i).mSyncId);
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index 06448d0..022ef61 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -28,6 +28,7 @@
 import android.annotation.Nullable;
 import android.content.res.Configuration;
 import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.media.projection.IMediaProjectionManager;
 import android.os.IBinder;
@@ -36,10 +37,12 @@
 import android.view.ContentRecordingSession;
 import android.view.ContentRecordingSession.RecordContent;
 import android.view.Display;
+import android.view.DisplayInfo;
 import android.view.SurfaceControl;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.server.display.feature.DisplayManagerFlags;
 
 /**
  * Manages content recording for a particular {@link DisplayContent}.
@@ -47,6 +50,16 @@
 final class ContentRecorder implements WindowContainerListener {
 
     /**
+     * Maximum acceptable anisotropy for the output image.
+     *
+     * Necessary to avoid unnecessary scaling when the anisotropy is almost the same, as it is not
+     * exact anyway. For external displays, we expect an anisoptry of about 2% even if the pixels
+     * are, in fact, square due to the imprecision of the display's actual size (rounded to the
+     * nearest cm).
+     */
+    private static final float MAX_ANISOTROPY = 0.025f;
+
+    /**
      * The display content this class is handling recording for.
      */
     @NonNull
@@ -87,15 +100,20 @@
     @Configuration.Orientation
     private int mLastOrientation = ORIENTATION_UNDEFINED;
 
+    private final boolean mCorrectForAnisotropicPixels;
+
     ContentRecorder(@NonNull DisplayContent displayContent) {
-        this(displayContent, new RemoteMediaProjectionManagerWrapper(displayContent.mDisplayId));
+        this(displayContent, new RemoteMediaProjectionManagerWrapper(displayContent.mDisplayId),
+                new DisplayManagerFlags().isConnectedDisplayManagementEnabled());
     }
 
     @VisibleForTesting
     ContentRecorder(@NonNull DisplayContent displayContent,
-            @NonNull MediaProjectionManagerWrapper mediaProjectionManager) {
+            @NonNull MediaProjectionManagerWrapper mediaProjectionManager,
+            boolean correctForAnisotropicPixels) {
         mDisplayContent = displayContent;
         mMediaProjectionManager = mediaProjectionManager;
+        mCorrectForAnisotropicPixels = correctForAnisotropicPixels;
     }
 
     /**
@@ -460,6 +478,33 @@
         }
     }
 
+    private void computeScaling(int inputSizeX, int inputSizeY,
+            float inputDpiX, float inputDpiY,
+            int outputSizeX, int outputSizeY,
+            float outputDpiX, float outputDpiY,
+            PointF scaleOut) {
+        float relAnisotropy = (inputDpiY / inputDpiX) / (outputDpiY / outputDpiX);
+        if (!mCorrectForAnisotropicPixels
+                || (relAnisotropy > (1 - MAX_ANISOTROPY) && relAnisotropy < (1 + MAX_ANISOTROPY))) {
+            // Calculate the scale to apply to the root mirror SurfaceControl to fit the size of the
+            // output surface.
+            float scaleX = outputSizeX / (float) inputSizeX;
+            float scaleY = outputSizeY / (float) inputSizeY;
+            float scale = Math.min(scaleX, scaleY);
+            scaleOut.x = scale;
+            scaleOut.y = scale;
+            return;
+        }
+
+        float relDpiX = outputDpiX / inputDpiX;
+        float relDpiY = outputDpiY / inputDpiY;
+
+        float scale = Math.min(outputSizeX / relDpiX / inputSizeX,
+                outputSizeY / relDpiY / inputSizeY);
+        scaleOut.x = scale * relDpiX;
+        scaleOut.y = scale * relDpiY;
+    }
+
     /**
      * Apply transformations to the mirrored surface to ensure the captured contents are scaled to
      * fit and centred in the output surface.
@@ -473,13 +518,19 @@
      */
     @VisibleForTesting void updateMirroredSurface(SurfaceControl.Transaction transaction,
             Rect recordedContentBounds, Point surfaceSize) {
-        // Calculate the scale to apply to the root mirror SurfaceControl to fit the size of the
-        // output surface.
-        float scaleX = surfaceSize.x / (float) recordedContentBounds.width();
-        float scaleY = surfaceSize.y / (float) recordedContentBounds.height();
-        float scale = Math.min(scaleX, scaleY);
-        int scaledWidth = Math.round(scale * (float) recordedContentBounds.width());
-        int scaledHeight = Math.round(scale * (float) recordedContentBounds.height());
+
+        DisplayInfo inputDisplayInfo = mRecordedWindowContainer.mDisplayContent.getDisplayInfo();
+        DisplayInfo outputDisplayInfo = mDisplayContent.getDisplayInfo();
+
+        PointF scale = new PointF();
+        computeScaling(recordedContentBounds.width(), recordedContentBounds.height(),
+                inputDisplayInfo.physicalXDpi, inputDisplayInfo.physicalYDpi,
+                surfaceSize.x, surfaceSize.y,
+                outputDisplayInfo.physicalXDpi, outputDisplayInfo.physicalYDpi,
+                scale);
+
+        int scaledWidth = Math.round(scale.x * (float) recordedContentBounds.width());
+        int scaledHeight = Math.round(scale.y * (float) recordedContentBounds.height());
 
         // Calculate the shift to apply to the root mirror SurfaceControl to centre the mirrored
         // contents in the output surface.
@@ -493,10 +544,10 @@
         }
 
         ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
-                "Content Recording: Apply transformations of shift %d x %d, scale %f, crop (aka "
-                        + "recorded content size) %d x %d for display %d; display has size %d x "
-                        + "%d; surface has size %d x %d",
-                shiftedX, shiftedY, scale, recordedContentBounds.width(),
+                "Content Recording: Apply transformations of shift %d x %d, scale %f x %f, crop "
+                        + "(aka recorded content size) %d x %d for display %d; display has size "
+                        + "%d x %d; surface has size %d x %d",
+                shiftedX, shiftedY, scale.x, scale.y, recordedContentBounds.width(),
                 recordedContentBounds.height(), mDisplayContent.getDisplayId(),
                 mDisplayContent.getConfiguration().screenWidthDp,
                 mDisplayContent.getConfiguration().screenHeightDp, surfaceSize.x, surfaceSize.y);
@@ -508,7 +559,7 @@
                         recordedContentBounds.height())
                 // Scale the root mirror SurfaceControl, based upon the size difference between the
                 // source (DisplayArea to capture) and output (surface the app reads images from).
-                .setMatrix(mRecordedSurface, scale, 0 /* dtdx */, 0 /* dtdy */, scale)
+                .setMatrix(mRecordedSurface, scale.x, 0 /* dtdx */, 0 /* dtdy */, scale.y)
                 // Position needs to be updated when the mirrored DisplayArea has changed, since
                 // the content will no longer be centered in the output surface.
                 .setPosition(mRecordedSurface, shiftedX /* x */, shiftedY /* y */);
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index 64a230e..823fbc9 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -28,6 +28,9 @@
  * black layers of varying opacity at various Z-levels which create the effect of a Dim.
  */
 public abstract class Dimmer {
+
+    static final boolean DIMMER_REFACTOR = Flags.dimmerRefactor();
+
     /**
      * The {@link WindowContainer} that our Dims are bounded to. We may be dimming on behalf of the
      * host, some controller of it, or one of the hosts children.
@@ -40,7 +43,7 @@
 
     // Constructs the correct type of dimmer
     static Dimmer create(WindowContainer host) {
-        return Flags.dimmerRefactor() ? new SmoothDimmer(host) : new LegacyDimmer(host);
+        return DIMMER_REFACTOR ? new SmoothDimmer(host) : new LegacyDimmer(host);
     }
 
     @NonNull
@@ -48,32 +51,34 @@
         return mHost;
     }
 
-    protected abstract void dim(
-            WindowContainer container, int relativeLayer, float alpha, int blurRadius);
+    /**
+     * Position the dim relatively to the dimming container.
+     * Normally called together with #setAppearance, it can be called alone to keep the dim parented
+     * to a visible container until the next dimming container is ready.
+     * If multiple containers call this method, only the changes relative to the topmost will be
+     * applied.
+     *
+     * For each call to {@link WindowContainer#prepareSurfaces()} the DimState will be reset, and
+     * the child of the host should call adjustRelativeLayer and {@link Dimmer#adjustAppearance} to
+     * continue dimming. Indeed, this method won't be able to keep dimming or get a new DimState
+     * without also adjusting the appearance.
+     * @param container      The container which to dim above. Should be a child of the host.
+     * @param relativeLayer  The position of the dim wrt the container
+     */
+    protected abstract void adjustRelativeLayer(WindowContainer container, int relativeLayer);
 
     /**
-     * Place a dim above the given container, which should be a child of the host container.
-     * for each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset
-     * and the child should call dimAbove again to request the Dim to continue.
-     *
-     * @param container The container which to dim above. Should be a child of our host.
-     * @param alpha     The alpha at which to Dim.
+     * Set the aspect of the dim layer, and request to keep dimming.
+     * For each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset, and the
+     * child should call setAppearance again to request the Dim to continue.
+     * If multiple containers call this method, only the changes relative to the topmost will be
+     * applied.
+     * @param container  Container requesting the dim
+     * @param alpha      Dim amount
+     * @param blurRadius Blur amount
      */
-    void dimAbove(@NonNull WindowContainer container, float alpha) {
-        dim(container, 1, alpha, 0);
-    }
-
-    /**
-     * Like {@link #dimAbove} but places the dim below the given container.
-     *
-     * @param container  The container which to dim below. Should be a child of our host.
-     * @param alpha      The alpha at which to Dim.
-     * @param blurRadius The amount of blur added to the Dim.
-     */
-
-    void dimBelow(@NonNull WindowContainer container, float alpha, int blurRadius) {
-        dim(container, -1, alpha, blurRadius);
-    }
+    protected abstract void adjustAppearance(
+            WindowContainer container, float alpha, int blurRadius);
 
     /**
      * Mark all dims as pending completion on the next call to {@link #updateDims}
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 02f5c21..cd114fc 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -68,6 +68,7 @@
     private final Rect mTmpRect = new Rect();
     private final InsetsStateController mStateController;
     private final InsetsSourceControl mFakeControl;
+    private final Consumer<Transaction> mSetLeashPositionConsumer;
     private @Nullable InsetsSourceControl mControl;
     private @Nullable InsetsControlTarget mControlTarget;
     private @Nullable InsetsControlTarget mPendingControlTarget;
@@ -85,16 +86,7 @@
     private boolean mInsetsHintStale = true;
     private @Flags int mFlagsFromFrameProvider;
     private @Flags int mFlagsFromServer;
-
-    private final Consumer<Transaction> mSetLeashPositionConsumer = t -> {
-        if (mControl != null) {
-            final SurfaceControl leash = mControl.getLeash();
-            if (leash != null) {
-                final Point position = mControl.getSurfacePosition();
-                t.setPosition(leash, position.x, position.y);
-            }
-        }
-    };
+    private boolean mHasPendingPosition;
 
     /** The visibility override from the current controlling window. */
     private boolean mClientVisible;
@@ -129,6 +121,21 @@
                 source.getId(), source.getType(), null /* leash */, false /* initialVisible */,
                 new Point(), Insets.NONE);
         mControllable = (InsetsPolicy.CONTROLLABLE_TYPES & source.getType()) != 0;
+        mSetLeashPositionConsumer = t -> {
+            if (mControl != null) {
+                final SurfaceControl leash = mControl.getLeash();
+                if (leash != null) {
+                    final Point position = mControl.getSurfacePosition();
+                    t.setPosition(leash, position.x, position.y);
+                }
+            }
+            if (mHasPendingPosition) {
+                mHasPendingPosition = false;
+                if (mPendingControlTarget != mControlTarget) {
+                    mStateController.notifyControlTargetChanged(mPendingControlTarget, this);
+                }
+            }
+        };
     }
 
     InsetsSource getSource() {
@@ -185,9 +192,8 @@
             mWindowContainer.getInsetsSourceProviders().put(mSource.getId(), this);
             if (mControllable) {
                 mWindowContainer.setControllableInsetProvider(this);
-                if (mPendingControlTarget != null) {
+                if (mPendingControlTarget != mControlTarget) {
                     updateControlForTarget(mPendingControlTarget, true /* force */);
-                    mPendingControlTarget = null;
                 }
             }
         }
@@ -344,6 +350,7 @@
                 changed = true;
                 if (windowState != null && windowState.getWindowFrames().didFrameSizeChange()
                         && windowState.mWinAnimator.getShown() && mWindowContainer.okToDisplay()) {
+                    mHasPendingPosition = true;
                     windowState.applyWithNextDraw(mSetLeashPositionConsumer);
                 } else {
                     Transaction t = mWindowContainer.getSyncTransaction();
@@ -465,18 +472,23 @@
             // to control the window for now.
             return;
         }
+        mPendingControlTarget = target;
 
         if (mWindowContainer != null && mWindowContainer.getSurfaceControl() == null) {
             // if window doesn't have a surface, set it null and return.
             setWindowContainer(null, null, null);
         }
         if (mWindowContainer == null) {
-            mPendingControlTarget = target;
             return;
         }
         if (target == mControlTarget && !force) {
             return;
         }
+        if (mHasPendingPosition) {
+            // Don't create a new leash while having a pending position. Otherwise, the position
+            // will be changed earlier than expected, which can cause flicker.
+            return;
+        }
         if (target == null) {
             // Cancelling the animation will invoke onAnimationCancelled, resetting all the fields.
             mWindowContainer.cancelAnimation();
@@ -618,6 +630,7 @@
         }
         pw.print(prefix);
         pw.print("mIsLeashReadyForDispatching="); pw.print(mIsLeashReadyForDispatching);
+        pw.print("mHasPendingPosition="); pw.print(mHasPendingPosition);
         pw.println();
         if (mWindowContainer != null) {
             pw.print(prefix + "mWindowContainer=");
@@ -631,7 +644,7 @@
             pw.print(prefix + "mControlTarget=");
             pw.println(mControlTarget);
         }
-        if (mPendingControlTarget != null) {
+        if (mPendingControlTarget != mControlTarget) {
             pw.print(prefix + "mPendingControlTarget=");
             pw.println(mPendingControlTarget);
         }
@@ -652,7 +665,8 @@
         if (mControlTarget != null && mControlTarget.getWindow() != null) {
             mControlTarget.getWindow().dumpDebug(proto, CONTROL_TARGET, logLevel);
         }
-        if (mPendingControlTarget != null && mPendingControlTarget.getWindow() != null) {
+        if (mPendingControlTarget != null && mPendingControlTarget != mControlTarget
+                && mPendingControlTarget.getWindow() != null) {
             mPendingControlTarget.getWindow().dumpDebug(proto, PENDING_CONTROL_TARGET, logLevel);
         }
         if (mFakeControlTarget != null && mFakeControlTarget.getWindow() != null) {
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 081ebe0..c4d0129 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -278,6 +278,12 @@
         notifyPendingInsetsControlChanged();
     }
 
+    void notifyControlTargetChanged(@Nullable InsetsControlTarget target,
+            InsetsSourceProvider provider) {
+        onControlTargetChanged(provider, target, false /* fake */);
+        notifyPendingInsetsControlChanged();
+    }
+
     void notifyControlRevoked(@NonNull InsetsControlTarget previousControlTarget,
             InsetsSourceProvider provider) {
         removeFromControlMaps(previousControlTarget, provider, false /* fake */);
diff --git a/services/core/java/com/android/server/wm/LegacyDimmer.java b/services/core/java/com/android/server/wm/LegacyDimmer.java
index ccf956e..3265e60 100644
--- a/services/core/java/com/android/server/wm/LegacyDimmer.java
+++ b/services/core/java/com/android/server/wm/LegacyDimmer.java
@@ -134,8 +134,9 @@
         boolean mAnimateExit = true;
 
         /**
-         * Used for Dims not associated with a WindowContainer. See {@link Dimmer#dimAbove} for
-         * details on Dim lifecycle.
+         * Used for Dims not associated with a WindowContainer.
+         * See {@link Dimmer#adjustRelativeLayer(WindowContainer, int)} for details on Dim
+         * lifecycle.
          */
         boolean mDontReset;
         SurfaceAnimator mSurfaceAnimator;
@@ -218,9 +219,8 @@
     }
 
     @Override
-    protected void dim(WindowContainer container, int relativeLayer, float alpha, int blurRadius) {
+    protected void adjustAppearance(WindowContainer container, float alpha, int blurRadius) {
         final DimState d = obtainDimState(container);
-
         if (d == null) {
             return;
         }
@@ -229,14 +229,21 @@
         // in the correct Z from lowest Z to highest. This ensures that the dim layer is always
         // relative to the highest Z layer with a dim.
         SurfaceControl.Transaction t = mHost.getPendingTransaction();
-        t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer);
         t.setAlpha(d.mDimLayer, alpha);
         t.setBackgroundBlurRadius(d.mDimLayer, blurRadius);
-
         d.mDimming = true;
     }
 
     @Override
+    protected void adjustRelativeLayer(WindowContainer container, int relativeLayer) {
+        final DimState d = mDimState;
+        if (d != null) {
+            SurfaceControl.Transaction t = mHost.getPendingTransaction();
+            t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer);
+        }
+    }
+
+    @Override
     boolean updateDims(SurfaceControl.Transaction t) {
         if (mDimState == null) {
             return false;
diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS
index f6c3640..f8c39d0 100644
--- a/services/core/java/com/android/server/wm/OWNERS
+++ b/services/core/java/com/android/server/wm/OWNERS
@@ -16,6 +16,7 @@
 mariiasand@google.com
 rgl@google.com
 yunfanc@google.com
+wilsonshih@google.com
 
 per-file BackgroundActivityStartController.java = set noparent
 per-file BackgroundActivityStartController.java = brufino@google.com, topjohnwu@google.com, achim@google.com, ogunwale@google.com, louischang@google.com, lus@google.com
diff --git a/services/core/java/com/android/server/wm/SmoothDimmer.java b/services/core/java/com/android/server/wm/SmoothDimmer.java
index 6ddbd2c..2549bbf 100644
--- a/services/core/java/com/android/server/wm/SmoothDimmer.java
+++ b/services/core/java/com/android/server/wm/SmoothDimmer.java
@@ -63,8 +63,9 @@
         boolean mAnimateExit = true;
 
         /**
-         * Used for Dims not associated with a WindowContainer. See {@link Dimmer#dimAbove} for
-         * details on Dim lifecycle.
+         * Used for Dims not associated with a WindowContainer.
+         * See {@link Dimmer#adjustRelativeLayer(WindowContainer, int)} for details on Dim
+         * lifecycle.
          */
         boolean mDontReset;
 
@@ -105,22 +106,34 @@
         }
 
         void setExitParameters(WindowContainer container) {
-            setRequestedParameters(container, -1, 0, 0);
+            setRequestedRelativeParent(container, -1 /* relativeLayer */);
+            setRequestedAppearance(0f /* alpha */, 0 /* blur */);
         }
+
         // Sets a requested change without applying it immediately
-        void setRequestedParameters(WindowContainer container, int relativeLayer, float alpha,
-                int blurRadius) {
-            mRequestedProperties.mDimmingContainer = container;
+        void setRequestedRelativeParent(WindowContainer relativeParent, int relativeLayer) {
+            mRequestedProperties.mDimmingContainer = relativeParent;
             mRequestedProperties.mRelativeLayer = relativeLayer;
+        }
+
+        // Sets a requested change without applying it immediately
+        void setRequestedAppearance(float alpha, int blurRadius) {
             mRequestedProperties.mAlpha = alpha;
             mRequestedProperties.mBlurRadius = blurRadius;
         }
 
         /**
          * Commit the last changes we received. Called after
-         * {@link Change#setRequestedParameters(WindowContainer, int, float, int)}
+         * {@link Change#setExitParameters(WindowContainer)},
+         * {@link Change#setRequestedRelativeParent(WindowContainer, int)}, or
+         * {@link Change#setRequestedAppearance(float, int)}
          */
         void applyChanges(SurfaceControl.Transaction t) {
+            if (mRequestedProperties.mDimmingContainer == null) {
+                Log.e(TAG, this + " does not have a dimming container. Have you forgotten to "
+                        + "call adjustRelativeLayer?");
+                return;
+            }
             if (mRequestedProperties.mDimmingContainer.mSurfaceControl == null) {
                 Log.w(TAG, "container " + mRequestedProperties.mDimmingContainer
                         + "does not have a surface");
@@ -276,14 +289,19 @@
     }
 
     @Override
-    protected void dim(WindowContainer container, int relativeLayer, float alpha, int blurRadius) {
+    protected void adjustAppearance(WindowContainer container, float alpha, int blurRadius) {
         final DimState d = obtainDimState(container);
-
-        mDimState.mRequestedProperties.mDimmingContainer = container;
-        mDimState.setRequestedParameters(container, relativeLayer, alpha, blurRadius);
+        mDimState.setRequestedAppearance(alpha, blurRadius);
         d.mDimming = true;
     }
 
+    @Override
+    protected void adjustRelativeLayer(WindowContainer container, int relativeLayer) {
+        if (mDimState != null) {
+            mDimState.setRequestedRelativeParent(container, relativeLayer);
+        }
+    }
+
     boolean updateDims(SurfaceControl.Transaction t) {
         if (mDimState == null) {
             return false;
diff --git a/services/core/java/com/android/server/wm/SnapshotController.java b/services/core/java/com/android/server/wm/SnapshotController.java
index 2e7ff7a..f68e392 100644
--- a/services/core/java/com/android/server/wm/SnapshotController.java
+++ b/services/core/java/com/android/server/wm/SnapshotController.java
@@ -85,7 +85,7 @@
             if (info.mWindowingMode == WINDOWING_MODE_PINNED) continue;
             if (info.mContainer.isActivityTypeHome()) continue;
             final Task task = info.mContainer.asTask();
-            if (task != null && !task.isVisibleRequested()) {
+            if (task != null && !task.mCreatedByOrganizer && !task.isVisibleRequested()) {
                 mTaskSnapshotController.recordSnapshot(task, info);
             }
             // Won't need to capture activity snapshot in close transition.
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index de7871e..8ac21e4 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -757,7 +757,7 @@
 
             final TransitionRequestInfo request = new TransitionRequestInfo(transition.mType,
                     startTaskInfo, pipTaskInfo, remoteTransition, displayChange,
-                    transition.getFlags());
+                    transition.getFlags(), transition.getSyncId());
 
             transition.mLogger.mRequestTimeNs = SystemClock.elapsedRealtimeNanos();
             transition.mLogger.mRequest = request;
@@ -1592,8 +1592,8 @@
         TransitionInfo mInfo;
 
         private String buildOnSendLog() {
-            StringBuilder sb = new StringBuilder("Sent Transition #").append(mSyncId)
-                    .append(" createdAt=").append(TimeUtils.logTimeOfDay(mCreateWallTimeMs));
+            StringBuilder sb = new StringBuilder("Sent Transition (#").append(mSyncId)
+                    .append(") createdAt=").append(TimeUtils.logTimeOfDay(mCreateWallTimeMs));
             if (mRequest != null) {
                 sb.append(" via request=").append(mRequest);
             }
@@ -1617,7 +1617,8 @@
         void logOnSend() {
             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "%s", buildOnSendLog());
             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "    startWCT=%s", mStartWCT);
-            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "    info=%s", mInfo);
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "    info=%s",
+                    mInfo.toString("    " /* prefix */));
         }
 
         private static String toMsString(long nanos) {
@@ -1625,8 +1626,8 @@
         }
 
         private String buildOnFinishLog() {
-            StringBuilder sb = new StringBuilder("Finish Transition #").append(mSyncId)
-                    .append(": created at ").append(TimeUtils.logTimeOfDay(mCreateWallTimeMs));
+            StringBuilder sb = new StringBuilder("Finish Transition (#").append(mSyncId)
+                    .append("): created at ").append(TimeUtils.logTimeOfDay(mCreateWallTimeMs));
             sb.append(" collect-started=").append(toMsString(mCollectTimeNs - mCreateTimeNs));
             if (mRequestTimeNs != 0) {
                 sb.append(" request-sent=").append(toMsString(mRequestTimeNs - mCreateTimeNs));
diff --git a/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java b/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java
index 41c1e79..c071396 100644
--- a/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java
+++ b/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java
@@ -70,6 +70,9 @@
     }
 
     boolean isVisibilityUnknown(ActivityRecord r) {
+        if (mUnknownApps.isEmpty()) {
+            return false;
+        }
         return mUnknownApps.containsKey(r);
     }
 
@@ -90,6 +93,9 @@
     }
 
     void appRemovedOrHidden(@NonNull ActivityRecord activity) {
+        if (mUnknownApps.isEmpty()) {
+            return;
+        }
         if (DEBUG_UNKNOWN_APP_VISIBILITY) {
             Slog.d(TAG, "App removed or hidden activity=" + activity);
         }
@@ -117,8 +123,11 @@
      * Notifies that {@param activity} has finished resuming.
      */
     void notifyAppResumedFinished(@NonNull ActivityRecord activity) {
-        if (mUnknownApps.containsKey(activity)
-                && mUnknownApps.get(activity) == UNKNOWN_STATE_WAITING_RESUME) {
+        if (mUnknownApps.isEmpty()) {
+            return;
+        }
+        final Integer state = mUnknownApps.get(activity);
+        if (state != null && state == UNKNOWN_STATE_WAITING_RESUME) {
             if (DEBUG_UNKNOWN_APP_VISIBILITY) {
                 Slog.d(TAG, "App resume finished activity=" + activity);
             }
@@ -130,13 +139,16 @@
      * Notifies that {@param activity} has relaid out.
      */
     void notifyRelayouted(@NonNull ActivityRecord activity) {
-        if (!mUnknownApps.containsKey(activity)) {
+        if (mUnknownApps.isEmpty()) {
+            return;
+        }
+        final Integer state = mUnknownApps.get(activity);
+        if (state == null) {
             return;
         }
         if (DEBUG_UNKNOWN_APP_VISIBILITY) {
             Slog.d(TAG, "App relayouted appWindow=" + activity);
         }
-        int state = mUnknownApps.get(activity);
         if (state == UNKNOWN_STATE_WAITING_RELAYOUT || activity.mStartingWindow != null) {
             mUnknownApps.put(activity, UNKNOWN_STATE_WAITING_VISIBILITY_UPDATE);
             mDisplayContent.notifyKeyguardFlagsChanged();
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 674ff48..33ef3c5 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -45,7 +45,6 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.SystemClock;
-import android.os.SystemProperties;
 import android.util.ArraySet;
 import android.util.MathUtils;
 import android.util.Slog;
@@ -117,8 +116,6 @@
 
     private boolean mShouldOffsetWallpaperCenter;
 
-    final boolean mIsLockscreenLiveWallpaperEnabled;
-
     private final Consumer<WindowState> mFindWallpapers = w -> {
         if (w.mAttrs.type == TYPE_WALLPAPER) {
             WallpaperWindowToken token = w.mToken.asWallpaperToken();
@@ -236,9 +233,6 @@
     WallpaperController(WindowManagerService service, DisplayContent displayContent) {
         mService = service;
         mDisplayContent = displayContent;
-        mIsLockscreenLiveWallpaperEnabled =
-                SystemProperties.getBoolean("persist.wm.debug.lockscreen_live_wallpaper", true);
-
         Resources resources = service.mContext.getResources();
         mMinWallpaperScale =
                 resources.getFloat(com.android.internal.R.dimen.config_wallpaperMinScale);
@@ -536,7 +530,7 @@
             window.mWallpaperY = y;
             window.mWallpaperXStep = xStep;
             window.mWallpaperYStep = yStep;
-            updateWallpaperOffsetLocked(window, true);
+            updateWallpaperOffsetLocked(window, !mService.mFlags.mWallpaperOffsetAsync);
         }
     }
 
@@ -561,7 +555,7 @@
         if (window.mWallpaperDisplayOffsetX != x || window.mWallpaperDisplayOffsetY != y)  {
             window.mWallpaperDisplayOffsetX = x;
             window.mWallpaperDisplayOffsetY = y;
-            updateWallpaperOffsetLocked(window, true);
+            updateWallpaperOffsetLocked(window, !mService.mFlags.mWallpaperOffsetAsync);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 50ef52a..15bd607 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -82,18 +82,16 @@
             return;
         }
         mShowWhenLocked = showWhenLocked;
-        if (mDisplayContent.mWallpaperController.mIsLockscreenLiveWallpaperEnabled) {
-            // Move the window token to the front (private) or back (showWhenLocked). This is
-            // possible
-            // because the DisplayArea underneath TaskDisplayArea only contains TYPE_WALLPAPER
-            // windows.
-            final int position = showWhenLocked ? POSITION_BOTTOM : POSITION_TOP;
+        // Move the window token to the front (private) or back (showWhenLocked). This is
+        // possible
+        // because the DisplayArea underneath TaskDisplayArea only contains TYPE_WALLPAPER
+        // windows.
+        final int position = showWhenLocked ? POSITION_BOTTOM : POSITION_TOP;
 
-            // Note: Moving all the way to the front or back breaks ordering based on addition
-            // times.
-            // We should never have more than one non-animating token of each type.
-            getParent().positionChildAt(position, this /* child */, false /*includingParents */);
-        }
+        // Note: Moving all the way to the front or back breaks ordering based on addition
+        // times.
+        // We should never have more than one non-animating token of each type.
+        getParent().positionChildAt(position, this /* child */, false /*includingParents */);
     }
 
     boolean canShowWhenLocked() {
@@ -117,7 +115,8 @@
         final WallpaperController wallpaperController = mDisplayContent.mWallpaperController;
         for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
             final WindowState wallpaper = mChildren.get(wallpaperNdx);
-            if (wallpaperController.updateWallpaperOffset(wallpaper, sync)) {
+            if (wallpaperController.updateWallpaperOffset(wallpaper,
+                    sync && !mWmService.mFlags.mWallpaperOffsetAsync)) {
                 // We only want to be synchronous with one wallpaper.
                 sync = false;
             }
diff --git a/services/core/java/com/android/server/wm/WindowManagerFlags.java b/services/core/java/com/android/server/wm/WindowManagerFlags.java
index 5b9acb2..4667710 100644
--- a/services/core/java/com/android/server/wm/WindowManagerFlags.java
+++ b/services/core/java/com/android/server/wm/WindowManagerFlags.java
@@ -47,5 +47,7 @@
 
     final boolean mWindowStateResizeItemFlag = Flags.windowStateResizeItemFlag();
 
+    final boolean mWallpaperOffsetAsync = Flags.wallpaperOffsetAsync();
+
     /* End Available Flags */
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9663f3a..88f72f9 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8461,16 +8461,18 @@
                     return true;
                 }
                 // For a task session, find the activity identified by the launch cookie.
-                final WindowContainerToken wct = getTaskWindowContainerTokenForLaunchCookie(
+                final WindowContainerInfo wci = getTaskWindowContainerInfoForLaunchCookie(
                         incomingSession.getTokenToRecord());
-                if (wct == null) {
+                if (wci == null) {
                     Slog.w(TAG, "Handling a new recording session; unable to find the "
                             + "WindowContainerToken");
                     return false;
                 }
                 // Replace the launch cookie in the session details with the task's
                 // WindowContainerToken.
-                incomingSession.setTokenToRecord(wct.asBinder());
+                incomingSession.setTokenToRecord(wci.getToken().asBinder());
+                // Also replace the UNKNOWN target UID with the actual UID.
+                incomingSession.setTargetUid(wci.getUid());
                 mContentRecordingController.setContentRecordingSessionLocked(incomingSession,
                         WindowManagerService.this);
                 return true;
@@ -8798,21 +8800,41 @@
         mAtmService.setFocusedTask(task.mTaskId, touchedActivity);
     }
 
+    @VisibleForTesting
+    static class WindowContainerInfo {
+        private final int mUid;
+        @NonNull private final WindowContainerToken mToken;
+
+        private WindowContainerInfo(int uid, @NonNull WindowContainerToken token) {
+            this.mUid = uid;
+            this.mToken = token;
+        }
+
+        public int getUid() {
+            return mUid;
+        }
+
+        @NonNull
+        public WindowContainerToken getToken() {
+            return mToken;
+        }
+    }
+
     /**
-     * Retrieve the {@link WindowContainerToken} of the task that contains the activity started
-     * with the given launch cookie.
+     * Retrieve the {@link WindowContainerInfo} of the task that contains the activity started with
+     * the given launch cookie.
      *
      * @param launchCookie the launch cookie set on the {@link ActivityOptions} when starting an
-     *                     activity
+     *     activity
      * @return a token representing the task containing the activity started with the given launch
-     * cookie, or {@code null} if the token couldn't be found.
+     *     cookie, or {@code null} if the token couldn't be found.
      */
     @VisibleForTesting
     @Nullable
-    WindowContainerToken getTaskWindowContainerTokenForLaunchCookie(@NonNull IBinder launchCookie) {
+    WindowContainerInfo getTaskWindowContainerInfoForLaunchCookie(@NonNull IBinder launchCookie) {
         // Find the activity identified by the launch cookie.
-        final ActivityRecord targetActivity = mRoot.getActivity(
-                activity -> activity.mLaunchCookie == launchCookie);
+        final ActivityRecord targetActivity =
+                mRoot.getActivity(activity -> activity.mLaunchCookie == launchCookie);
         if (targetActivity == null) {
             Slog.w(TAG, "Unable to find the activity for this launch cookie");
             return null;
@@ -8827,7 +8849,7 @@
             Slog.w(TAG, "Unable to find the WindowContainerToken for " + targetActivity.getName());
             return null;
         }
-        return taskWindowContainerToken;
+        return new WindowContainerInfo(targetActivity.getUid(), taskWindowContainerToken);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 7f36aec..3a793e9 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5136,8 +5136,8 @@
 
     private void applyDims() {
         if (((mAttrs.flags & FLAG_DIM_BEHIND) != 0 || shouldDrawBlurBehind())
-                && mToken.isVisibleRequested() && isVisibleNow() && !mHidden
-                && mTransitionController.canApplyDim(getTask())) {
+                && (Dimmer.DIMMER_REFACTOR ? mWinAnimator.getShown() : isVisibleNow())
+                && !mHidden && mTransitionController.canApplyDim(getTask())) {
             // Only show the Dimmer when the following is satisfied:
             // 1. The window has the flag FLAG_DIM_BEHIND or blur behind is requested
             // 2. The WindowToken is not hidden so dims aren't shown when the window is exiting.
@@ -5147,7 +5147,13 @@
             mIsDimming = true;
             final float dimAmount = (mAttrs.flags & FLAG_DIM_BEHIND) != 0 ? mAttrs.dimAmount : 0;
             final int blurRadius = shouldDrawBlurBehind() ? mAttrs.getBlurBehindRadius() : 0;
-            getDimmer().dimBelow(this, dimAmount, blurRadius);
+            // If the window is visible from surface flinger perspective (mWinAnimator.getShown())
+            // but not window manager visible (!isVisibleNow()), it can still be the parent of the
+            // dim, but can not create a new surface or continue a dim alone.
+            if (isVisibleNow()) {
+                getDimmer().adjustAppearance(this, dimAmount, blurRadius);
+            }
+            getDimmer().adjustRelativeLayer(this, -1 /* relativeLayer */);
         }
     }
 
@@ -5207,12 +5213,17 @@
     void prepareSurfaces() {
         mIsDimming = false;
         if (mHasSurface) {
-            applyDims();
+            if (!Dimmer.DIMMER_REFACTOR) {
+                applyDims();
+            }
             updateSurfacePositionNonOrganized();
             // Send information to SurfaceFlinger about the priority of the current window.
             updateFrameRateSelectionPriorityIfNeeded();
             updateScaleIfNeeded();
             mWinAnimator.prepareSurfaceLocked(getSyncTransaction());
+            if (Dimmer.DIMMER_REFACTOR) {
+                applyDims();
+            }
         }
         super.prepareSurfaces();
     }
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 709d5e3..24ee163 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -189,7 +189,7 @@
         "android.hardware.thermal@1.0",
         "android.hardware.thermal-V1-ndk",
         "android.hardware.tv.input@1.0",
-        "android.hardware.tv.input-V1-ndk",
+        "android.hardware.tv.input-V2-ndk",
         "android.hardware.vibrator-V2-cpp",
         "android.hardware.vibrator@1.0",
         "android.hardware.vibrator@1.1",
@@ -244,5 +244,5 @@
 
 filegroup {
     name: "lib_oomConnection_native",
-    srcs: ["com_android_server_am_OomConnection.cpp",],
+    srcs: ["com_android_server_am_OomConnection.cpp"],
 }
diff --git a/services/core/jni/tvinput/JTvInputHal.cpp b/services/core/jni/tvinput/JTvInputHal.cpp
index c736617..dc05462 100644
--- a/services/core/jni/tvinput/JTvInputHal.cpp
+++ b/services/core/jni/tvinput/JTvInputHal.cpp
@@ -368,12 +368,20 @@
 }
 
 JTvInputHal::TvMessageEventWrapper JTvInputHal::TvMessageEventWrapper::createEventWrapper(
-        const AidlTvMessageEvent& aidlTvMessageEvent) {
+        const AidlTvMessageEvent& aidlTvMessageEvent, bool isLegacyMessage) {
+    auto messageList = aidlTvMessageEvent.messages;
     TvMessageEventWrapper event;
-    event.messages.insert(event.messages.begin(), std::begin(aidlTvMessageEvent.messages) + 1,
-                          std::end(aidlTvMessageEvent.messages));
+    // Handle backwards compatibility for V1
+    if (isLegacyMessage) {
+        event.deviceId = messageList[0].groupId;
+        event.messages.insert(event.messages.begin(), std::begin(messageList) + 1,
+                              std::end(messageList));
+    } else {
+        event.deviceId = aidlTvMessageEvent.deviceId;
+        event.messages.insert(event.messages.begin(), std::begin(messageList),
+                              std::end(messageList));
+    }
     event.streamId = aidlTvMessageEvent.streamId;
-    event.deviceId = aidlTvMessageEvent.messages[0].groupId;
     event.type = aidlTvMessageEvent.type;
     return event;
 }
@@ -449,15 +457,30 @@
 ::ndk::ScopedAStatus JTvInputHal::TvInputCallback::notifyTvMessageEvent(
         const AidlTvMessageEvent& event) {
     const std::string DEVICE_ID_SUBTYPE = "device_id";
-    if (event.messages.size() > 1 && event.messages[0].subType == DEVICE_ID_SUBTYPE) {
-        mHal->mLooper
-                ->sendMessage(new NotifyTvMessageHandler(mHal,
-                                                         TvMessageEventWrapper::createEventWrapper(
-                                                                 event)),
-                              static_cast<int>(event.type));
+    ::ndk::ScopedAStatus status = ::ndk::ScopedAStatus::ok();
+    int32_t aidlVersion = 0;
+    if (mHal->mTvInput->getAidlInterfaceVersion(&aidlVersion).isOk() && event.messages.size() > 0) {
+        bool validLegacyMessage = aidlVersion == 1 &&
+                event.messages[0].subType == DEVICE_ID_SUBTYPE && event.messages.size() > 1;
+        bool validTvMessage = aidlVersion > 1 && event.messages.size() > 0;
+        if (validLegacyMessage || validTvMessage) {
+            mHal->mLooper->sendMessage(
+                    new NotifyTvMessageHandler(mHal,
+                                               TvMessageEventWrapper::
+                                                       createEventWrapper(event,
+                                                                          validLegacyMessage)),
+                    static_cast<int>(event.type));
+        } else {
+            status = ::ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+            ALOGE("The TVMessage event was malformed for HAL version: %d", aidlVersion);
+        }
+    } else {
+        status = ::ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+        ALOGE("The TVMessage event was empty or the HAL version (version: %d) could not "
+              "be inferred.",
+              aidlVersion);
     }
-
-    return ::ndk::ScopedAStatus::ok();
+    return status;
 }
 
 JTvInputHal::ITvInputWrapper::ITvInputWrapper(std::shared_ptr<AidlITvInput>& aidlTvInput)
@@ -521,4 +544,12 @@
     }
 }
 
+::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::getAidlInterfaceVersion(int32_t* _aidl_return) {
+    if (mIsHidl) {
+        return ::ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+    } else {
+        return mAidlTvInput->getInterfaceVersion(_aidl_return);
+    }
+}
+
 } // namespace android
diff --git a/services/core/jni/tvinput/JTvInputHal.h b/services/core/jni/tvinput/JTvInputHal.h
index 1d8d162..6026a10 100644
--- a/services/core/jni/tvinput/JTvInputHal.h
+++ b/services/core/jni/tvinput/JTvInputHal.h
@@ -138,7 +138,7 @@
         TvMessageEventWrapper() {}
 
         static TvMessageEventWrapper createEventWrapper(
-                const AidlTvMessageEvent& aidlTvMessageEvent);
+                const AidlTvMessageEvent& aidlTvMessageEvent, bool isLegacyMessage);
 
         int streamId;
         int deviceId;
@@ -195,6 +195,7 @@
         ::ndk::ScopedAStatus getTvMessageQueueDesc(
                 MQDescriptor<int8_t, SynchronizedReadWrite>* out_queue, int32_t in_deviceId,
                 int32_t in_streamId);
+        ::ndk::ScopedAStatus getAidlInterfaceVersion(int32_t* _aidl_return);
 
     private:
         ::ndk::ScopedAStatus hidlSetCallback(const std::shared_ptr<TvInputCallback>& in_callback);
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index debd891..215934f 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -272,12 +272,12 @@
         <xs:element name="brightnessDecreaseDebounceMillis" type="xs:nonNegativeInteger">
             <xs:annotation name="final"/>
         </xs:element>
-        <!-- Animation time for brightness increase in millis -->
-        <xs:element  name="brightnessIncreaseDurationMillis" type="xs:nonNegativeInteger">
+        <!-- Animation speed for brightness increase. In framework brightness units per second. -->
+        <xs:element  name="screenBrightnessRampIncrease" type="nonNegativeDecimal">
             <xs:annotation name="final"/>
         </xs:element>
-        <!-- Animation time for brightness decrease in millis -->
-        <xs:element name="brightnessDecreaseDurationMillis" type="xs:nonNegativeInteger">
+        <!-- Animation speed for brightness decrease. In framework brightness units per second. -->
+        <xs:element name="screenBrightnessRampDecrease" type="nonNegativeDecimal">
             <xs:annotation name="final"/>
         </xs:element>
     </xs:complexType>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 2d27f0c..f7e0043 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -179,15 +179,15 @@
   public class HdrBrightnessConfig {
     ctor public HdrBrightnessConfig();
     method public final java.math.BigInteger getBrightnessDecreaseDebounceMillis();
-    method public final java.math.BigInteger getBrightnessDecreaseDurationMillis();
     method public final java.math.BigInteger getBrightnessIncreaseDebounceMillis();
-    method public final java.math.BigInteger getBrightnessIncreaseDurationMillis();
     method @NonNull public final com.android.server.display.config.NonNegativeFloatToFloatMap getBrightnessMap();
+    method public final java.math.BigDecimal getScreenBrightnessRampDecrease();
+    method public final java.math.BigDecimal getScreenBrightnessRampIncrease();
     method public final void setBrightnessDecreaseDebounceMillis(java.math.BigInteger);
-    method public final void setBrightnessDecreaseDurationMillis(java.math.BigInteger);
     method public final void setBrightnessIncreaseDebounceMillis(java.math.BigInteger);
-    method public final void setBrightnessIncreaseDurationMillis(java.math.BigInteger);
     method public final void setBrightnessMap(@NonNull com.android.server.display.config.NonNegativeFloatToFloatMap);
+    method public final void setScreenBrightnessRampDecrease(java.math.BigDecimal);
+    method public final void setScreenBrightnessRampIncrease(java.math.BigDecimal);
   }
 
   public class HighBrightnessMode {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 25e8475..922f69c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -40,6 +40,7 @@
 import android.app.admin.PolicyValue;
 import android.app.admin.TargetUser;
 import android.app.admin.UserRestrictionPolicyKey;
+import android.app.admin.flags.FlagUtils;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -65,7 +66,6 @@
 import com.android.internal.util.XmlUtils;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
-import com.android.server.devicepolicy.flags.FlagUtils;
 import com.android.server.utils.Slogf;
 
 import libcore.io.IoUtils;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 5a620a3..8509155 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -330,6 +330,7 @@
 import android.app.admin.UnsafeStateException;
 import android.app.admin.UserRestrictionPolicyKey;
 import android.app.admin.WifiSsidPolicy;
+import android.app.admin.flags.FlagUtils;
 import android.app.backup.IBackupManager;
 import android.app.compat.CompatChanges;
 import android.app.role.OnRoleHoldersChangedListener;
@@ -490,7 +491,6 @@
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
 import com.android.server.devicepolicy.ActiveAdmin.TrustAgentInfo;
-import com.android.server.devicepolicy.flags.FlagUtils;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.pdb.PersistentDataBlockManagerInternal;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/flags/Android.bp b/services/devicepolicy/java/com/android/server/devicepolicy/flags/Android.bp
deleted file mode 100644
index 1a45782..0000000
--- a/services/devicepolicy/java/com/android/server/devicepolicy/flags/Android.bp
+++ /dev/null
@@ -1,16 +0,0 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-aconfig_declarations {
-    name: "device_policy_aconfig_flags",
-    package: "com.android.server.devicepolicy.flags",
-    srcs: [
-        "flags.aconfig",
-    ],
-}
-
-java_aconfig_library {
-    name: "device_policy_aconfig_flags_lib",
-    aconfig_declarations: "device_policy_aconfig_flags",
-}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig b/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig
deleted file mode 100644
index 0dde496..0000000
--- a/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig
+++ /dev/null
@@ -1,14 +0,0 @@
-package: "com.android.server.devicepolicy.flags"
-
-flag {
-  name: "policy_engine_migration_v2_enabled"
-  namespace: "enterprise"
-  description: "V2 of the policy engine migrations for Android V"
-  bug: "289520697"
-}
-flag {
-  name: "device_policy_size_tracking_enabled"
-  namespace: "enterprise"
-  description: "Add feature to track the total policy size and have a max threshold."
-  bug: "281543351"
-}
\ No newline at end of file
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index c26aee8..924e2f8 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -338,6 +338,8 @@
             "com.android.clockwork.modes.ModeManagerService";
     private static final String WEAR_DISPLAY_SERVICE_CLASS =
             "com.android.clockwork.display.WearDisplayService";
+    private static final String WEAR_DEBUG_SERVICE_CLASS =
+            "com.android.clockwork.debug.WearDebugService";
     private static final String WEAR_TIME_SERVICE_CLASS =
             "com.android.clockwork.time.WearTimeService";
     private static final String WEAR_SETTINGS_SERVICE_CLASS =
@@ -2636,6 +2638,12 @@
             mSystemServiceManager.startService(WEAR_DISPLAY_SERVICE_CLASS);
             t.traceEnd();
 
+            if (Build.IS_DEBUGGABLE) {
+                t.traceBegin("StartWearDebugService");
+                mSystemServiceManager.startService(WEAR_DEBUG_SERVICE_CLASS);
+                t.traceEnd();
+            }
+
             t.traceBegin("StartWearTimeService");
             mSystemServiceManager.startService(WEAR_TIME_SERVICE_CLASS);
             t.traceEnd();
diff --git a/services/midi/Android.bp b/services/midi/Android.bp
index 4b5f8a7..a385fe3 100644
--- a/services/midi/Android.bp
+++ b/services/midi/Android.bp
@@ -18,8 +18,8 @@
     name: "services.midi",
     defaults: ["platform_service_defaults"],
     srcs: [":services.midi-sources"],
-    libs: ["services.core"],
-    static_libs: [
+    libs: [
+        "services.core",
         "aconfig_midi_flags_java_lib",
     ],
 }
diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
index f3ac7d5..12cd0f6 100644
--- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
+++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
@@ -143,8 +143,8 @@
             val result: Result,
             val componentName: ComponentName? = ComponentName(pkgName, COMPONENT_CLASS_NAME)
         ) {
-            constructor(pkgName: String, appType: AppType, exception: Class<out Exception>)
-                    : this(pkgName, appType, Result.Exception(exception))
+            constructor(pkgName: String, appType: AppType, exception: Class<out Exception>) :
+                    this(pkgName, appType, Result.Exception(exception))
 
             val expectedLabel = when (result) {
                 Result.Changed, Result.ChangedWithoutNotify, Result.NotChanged -> TEST_LABEL
@@ -299,11 +299,9 @@
                     .hideAsFinal()
 
     private fun makePkgSetting(pkgName: String, pkg: AndroidPackageInternal) =
-        PackageSetting(
-            pkgName, null, File("/test"),
-            null, null, null, null, 0, 0, 0, 0, null, null, null, null, null,
-            UUID.fromString("3f9d52b7-d7b4-406a-a1da-d9f19984c72c")
-        ).apply {
+        PackageSetting(pkgName, null, File("/test"), 0, 0,
+                UUID.fromString("3f9d52b7-d7b4-406a-a1da-d9f19984c72c"))
+        .apply {
             if (params.isSystem) {
                 this.flags = this.flags or ApplicationInfo.FLAG_SYSTEM
             }
@@ -373,7 +371,7 @@
             whenever(this.isCallerRecents(anyInt())) { false }
         }
         val mockAppsFilter: AppsFilterImpl = mockThrowOnUnmocked {
-            whenever(this.shouldFilterApplication(any<PackageDataSnapshot>(), anyInt(), 
+            whenever(this.shouldFilterApplication(any<PackageDataSnapshot>(), anyInt(),
                     any<PackageSetting>(), any<PackageSetting>(), anyInt())) { false }
             whenever(this.snapshot()) { this@mockThrowOnUnmocked }
             whenever(registerObserver(any())).thenCallRealMethod()
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index 952cfc4..aaad669 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -43,6 +43,7 @@
 
 import android.annotation.NonNull;
 import android.app.PropertyInvalidatedCache;
+import android.content.ComponentName;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.SuspendDialogInfo;
@@ -873,12 +874,20 @@
                 .setUid(packageSetting.getAppId())
                 .hideAsFinal());
 
-        ArchiveState archiveState = new ArchiveState(
-                List.of(new ArchiveState.ArchiveActivityInfo("title1", Path.of("/path1"),
-                                Path.of("/monochromePath1")),
-                        new ArchiveState.ArchiveActivityInfo("title2", Path.of("/path2"),
-                                Path.of("/monochromePath2"))),
-                "installerTitle");
+        ArchiveState archiveState =
+                new ArchiveState(
+                        List.of(
+                                new ArchiveState.ArchiveActivityInfo(
+                                        "title1",
+                                        new ComponentName("pkg1", "class1"),
+                                        Path.of("/path1"),
+                                        Path.of("/monochromePath1")),
+                                new ArchiveState.ArchiveActivityInfo(
+                                        "title2",
+                                        new ComponentName("pkg2", "class2"),
+                                        Path.of("/path2"),
+                                        Path.of("/monochromePath2"))),
+                        "installerTitle");
         packageSetting.modifyUserState(UserHandle.SYSTEM.getIdentifier()).setArchiveState(
                 archiveState);
         settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
@@ -958,20 +967,12 @@
                 PACKAGE_NAME,
                 REAL_PACKAGE_NAME,
                 INITIAL_CODE_PATH /*codePath*/,
-                null /*legacyNativeLibraryPathString*/,
-                "x86_64" /*primaryCpuAbiString*/,
-                "x86" /*secondaryCpuAbiString*/,
-                null /*cpuAbiOverrideString*/,
-                INITIAL_VERSION_CODE,
                 ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_HAS_CODE,
                 ApplicationInfo.PRIVATE_FLAG_PRIVILEGED|ApplicationInfo.PRIVATE_FLAG_HIDDEN,
-                0,
-                null /*usesSdkLibraries*/,
-                null /*usesSdkLibrariesVersions*/,
-                null /*usesStaticLibraries*/,
-                null /*usesStaticLibrariesVersions*/,
-                null /*mimeGroups*/,
-                UUID.randomUUID());
+                UUID.randomUUID())
+                .setPrimaryCpuAbi("x86_64")
+                .setSecondaryCpuAbi("x86")
+                .setLongVersionCode(INITIAL_VERSION_CODE);
         origPkgSetting01.setPkg(mockAndroidPackage(origPkgSetting01));
         final PackageSetting testPkgSetting01 = new PackageSetting(origPkgSetting01);
         verifySettingCopy(origPkgSetting01, testPkgSetting01);
@@ -980,23 +981,15 @@
     @Test
     public void testPackageStateCopy02() {
         final PackageSetting origPkgSetting01 = new PackageSetting(
-                PACKAGE_NAME /*pkgName*/,
-                REAL_PACKAGE_NAME /*realPkgName*/,
+                PACKAGE_NAME,
+                REAL_PACKAGE_NAME,
                 INITIAL_CODE_PATH /*codePath*/,
-                null /*legacyNativeLibraryPathString*/,
-                "x86_64" /*primaryCpuAbiString*/,
-                "x86" /*secondaryCpuAbiString*/,
-                null /*cpuAbiOverrideString*/,
-                INITIAL_VERSION_CODE,
                 ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_HAS_CODE,
                 ApplicationInfo.PRIVATE_FLAG_PRIVILEGED|ApplicationInfo.PRIVATE_FLAG_HIDDEN,
-                0,
-                null /*usesSdkLibraries*/,
-                null /*usesSdkLibrariesVersions*/,
-                null /*usesStaticLibraries*/,
-                null /*usesStaticLibrariesVersions*/,
-                null /*mimeGroups*/,
-                UUID.randomUUID());
+                UUID.randomUUID())
+                .setPrimaryCpuAbi("x86_64")
+                .setSecondaryCpuAbi("x86")
+                .setLongVersionCode(INITIAL_VERSION_CODE);
         origPkgSetting01.setUserState(0, 100, 100, 1, true, false, false, false, 0, null, false,
                 false, "lastDisabledCaller", new ArraySet<>(new String[]{"enabledComponent1"}),
                 new ArraySet<>(new String[]{"disabledComponent1"}), 0, 0, "harmfulAppWarning",
@@ -1019,20 +1012,10 @@
                 PACKAGE_NAME /*pkgName*/,
                 REAL_PACKAGE_NAME /*realPkgName*/,
                 UPDATED_CODE_PATH /*codePath*/,
-                null /*legacyNativeLibraryPathString*/,
-                null /*primaryCpuAbiString*/,
-                null /*secondaryCpuAbiString*/,
-                null /*cpuAbiOverrideString*/,
-                UPDATED_VERSION_CODE,
                 0 /*pkgFlags*/,
                 0 /*pkgPrivateFlags*/,
-                0,
-                null /*usesSdkLibraries*/,
-                null /*usesSdkLibrariesVersions*/,
-                null /*usesStaticLibraries*/,
-                null /*usesStaticLibrariesVersions*/,
-                null /*mimeGroups*/,
-                UUID.randomUUID());
+                UUID.randomUUID())
+                .setLongVersionCode(UPDATED_VERSION_CODE);
         testPkgSetting01.copyPackageSetting(origPkgSetting01, true);
         verifySettingCopy(origPkgSetting01, testPkgSetting01);
         verifyUserStatesCopy(origPkgSetting01.readUserState(0),
@@ -1708,20 +1691,13 @@
                 PACKAGE_NAME,
                 REAL_PACKAGE_NAME,
                 INITIAL_CODE_PATH /*codePath*/,
-                null /*legacyNativeLibraryPathString*/,
-                "x86_64" /*primaryCpuAbiString*/,
-                "x86" /*secondaryCpuAbiString*/,
-                null /*cpuAbiOverrideString*/,
-                INITIAL_VERSION_CODE,
                 pkgFlags,
                 0 /*privateFlags*/,
-                sharedUserId,
-                null /*usesSdkLibraries*/,
-                null /*usesSdkLibrariesVersions*/,
-                null /*usesStaticLibraries*/,
-                null /*usesStaticLibrariesVersions*/,
-                null /*mimeGroups*/,
-                UUID.randomUUID());
+                UUID.randomUUID())
+                .setPrimaryCpuAbi("x86_64")
+                .setSecondaryCpuAbi("x86")
+                .setLongVersionCode(INITIAL_VERSION_CODE)
+                .setSharedUserAppId(sharedUserId);
     }
 
     private PackageSetting createPackageSetting(String packageName) {
@@ -1729,20 +1705,12 @@
                 packageName,
                 packageName,
                 INITIAL_CODE_PATH /*codePath*/,
-                null /*legacyNativeLibraryPathString*/,
-                "x86_64" /*primaryCpuAbiString*/,
-                "x86" /*secondaryCpuAbiString*/,
-                null /*cpuAbiOverrideString*/,
-                INITIAL_VERSION_CODE,
                 0,
                 0 /*privateFlags*/,
-                0,
-                null /*usesSdkLibraries*/,
-                null /*usesSdkLibrariesVersions*/,
-                null /*usesStaticLibraries*/,
-                null /*usesStaticLibrariesVersions*/,
-                null /*mimeGroups*/,
-                UUID.randomUUID());
+                UUID.randomUUID())
+                .setPrimaryCpuAbi("x86_64")
+                .setSecondaryCpuAbi("x86")
+                .setLongVersionCode(INITIAL_VERSION_CODE);
     }
 
     static @NonNull List<UserInfo> createFakeUsers() {
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
index 58ae740..87a297b 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
@@ -24,6 +24,7 @@
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 
+import android.content.ComponentName;
 import android.content.pm.PackageManager;
 import android.content.pm.SuspendDialogInfo;
 import android.content.pm.overlay.OverlayPaths;
@@ -192,8 +193,8 @@
         return new SuspendParams(dialogInfo, appExtras, launcherExtras);
     }
 
-    private static PersistableBundle createPersistableBundle(String lKey, long lValue, String sKey,
-            String sValue, String dKey, double dValue) {
+    private static PersistableBundle createPersistableBundle(
+            String lKey, long lValue, String sKey, String sValue, String dKey, double dValue) {
         final PersistableBundle result = new PersistableBundle(3);
         if (lKey != null) {
             result.putLong("com.unit_test." + lKey, lValue);
@@ -320,6 +321,7 @@
             assertEquals(0L, state.getLastPackageUsageTimeInMills()[i]);
         }
     }
+
     private static void assertLastPackageUsageSet(
             PackageStateUnserialized state, int reason, long value) throws Exception {
         for (int i = state.getLastPackageUsageTimeInMills().length - 1; i >= 0; --i) {
@@ -330,6 +332,7 @@
             }
         }
     }
+
     @Test
     public void testPackageUseReasons() throws Exception {
         PackageSetting packageSetting = Mockito.mock(PackageSetting.class);
@@ -377,6 +380,7 @@
         assertTrue(testState.setOverlayPaths(new OverlayPaths.Builder().build()));
         assertFalse(testState.setOverlayPaths(null));
     }
+
     @Test
     public void testSharedLibOverlayPaths() {
         final PackageUserStateImpl testState = new PackageUserStateImpl();
@@ -401,8 +405,12 @@
     @Test
     public void archiveState() {
         PackageUserStateImpl packageUserState = new PackageUserStateImpl();
-        ArchiveState.ArchiveActivityInfo archiveActivityInfo = new ArchiveState.ArchiveActivityInfo(
-                "appTitle", Path.of("/path1"), Path.of("/path2"));
+        ArchiveState.ArchiveActivityInfo archiveActivityInfo =
+                new ArchiveState.ArchiveActivityInfo(
+                        "appTitle",
+                        new ComponentName("pkg", "class"),
+                        Path.of("/path1"),
+                        Path.of("/path2"));
         ArchiveState archiveState = new ArchiveState(List.of(archiveActivityInfo),
                 "installerTitle");
         packageUserState.setArchiveState(archiveState);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
index c12aedb..a400f12 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
@@ -90,7 +90,9 @@
                 .append("\n    isSlowChange:")
                 .append(displayBrightnessState.isSlowChange())
                 .append("\n    maxBrightness:")
-                .append(displayBrightnessState.getMaxBrightness());
+                .append(displayBrightnessState.getMaxBrightness())
+                .append("\n    customAnimationRate:")
+                .append(displayBrightnessState.getCustomAnimationRate());
         return sb.toString();
     }
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index c37d21a..179a9d5 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -570,9 +570,9 @@
         assertNotNull(data);
         assertEquals(2, data.mMaxBrightnessLimits.size());
         assertEquals(13000, data.mBrightnessDecreaseDebounceMillis);
-        assertEquals(10000, data.mBrightnessDecreaseDurationMillis);
+        assertEquals(0.1f, data.mScreenBrightnessRampDecrease, SMALL_DELTA);
         assertEquals(1000, data.mBrightnessIncreaseDebounceMillis);
-        assertEquals(11000, data.mBrightnessIncreaseDurationMillis);
+        assertEquals(0.11f, data.mScreenBrightnessRampIncrease, SMALL_DELTA);
 
         assertEquals(0.3f, data.mMaxBrightnessLimits.get(500f), SMALL_DELTA);
         assertEquals(0.6f, data.mMaxBrightnessLimits.get(1200f), SMALL_DELTA);
@@ -841,9 +841,9 @@
               + "        </point>\n"
               + "    </brightnessMap>\n"
               + "    <brightnessIncreaseDebounceMillis>1000</brightnessIncreaseDebounceMillis>\n"
-              + "    <brightnessIncreaseDurationMillis>11000</brightnessIncreaseDurationMillis>\n"
+              + "    <screenBrightnessRampIncrease>0.11</screenBrightnessRampIncrease>\n"
               + "    <brightnessDecreaseDebounceMillis>13000</brightnessDecreaseDebounceMillis>\n"
-              + "    <brightnessDecreaseDurationMillis>10000</brightnessDecreaseDurationMillis>\n"
+              + "    <screenBrightnessRampDecrease>0.1</screenBrightnessRampDecrease>\n"
               + "</hdrBrightnessConfig>";
     }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
index 8b54d6d2..47521d1 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -74,6 +74,7 @@
 import com.android.server.am.BatteryStatsService;
 import com.android.server.display.RampAnimator.DualRampAnimator;
 import com.android.server.display.brightness.BrightnessEvent;
+import com.android.server.display.brightness.clamper.BrightnessClamperController;
 import com.android.server.display.brightness.clamper.HdrClamper;
 import com.android.server.display.color.ColorDisplayService;
 import com.android.server.display.feature.DisplayManagerFlags;
@@ -1314,6 +1315,54 @@
     }
 
     @Test
+    public void testRampRateForClampersControllerApplied() {
+        float transitionRate = 1.5f;
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
+        when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
+        when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer(
+                invocation -> DisplayBrightnessState.builder()
+                        .setIsSlowChange(invocation.getArgument(2))
+                        .setBrightness(invocation.getArgument(1))
+                        .setMaxBrightness(PowerManager.BRIGHTNESS_MAX)
+                        .setCustomAnimationRate(transitionRate).build());
+
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.animator, atLeastOnce()).animateTo(anyFloat(), anyFloat(),
+                eq(transitionRate), anyBoolean());
+    }
+
+    @Test
+    public void testRampRateForClampersControllerNotApplied_ifDoze() {
+        float transitionRate = 1.5f;
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+        dpr.dozeScreenState = Display.STATE_UNKNOWN;
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
+        when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
+        when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer(
+                invocation -> DisplayBrightnessState.builder()
+                        .setIsSlowChange(invocation.getArgument(2))
+                        .setBrightness(invocation.getArgument(1))
+                        .setMaxBrightness(PowerManager.BRIGHTNESS_MAX)
+                        .setCustomAnimationRate(transitionRate).build());
+
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.animator, atLeastOnce()).animateTo(anyFloat(), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE), anyBoolean());
+        verify(mHolder.animator, never()).animateTo(anyFloat(), anyFloat(),
+                eq(transitionRate), anyBoolean());
+    }
+
+    @Test
     @RequiresFlagsDisabled(Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1)
     public void testRampMaxTimeInteractiveThenIdle() {
         // Send a display power request
@@ -1637,13 +1686,20 @@
                 mock(ScreenOffBrightnessSensorController.class);
         final HighBrightnessModeController hbmController = mock(HighBrightnessModeController.class);
         final HdrClamper hdrClamper = mock(HdrClamper.class);
+        BrightnessClamperController clamperController = mock(BrightnessClamperController.class);
 
         when(hbmController.getCurrentBrightnessMax()).thenReturn(PowerManager.BRIGHTNESS_MAX);
+        when(clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer(
+                invocation -> DisplayBrightnessState.builder()
+                        .setIsSlowChange(invocation.getArgument(2))
+                        .setBrightness(invocation.getArgument(1))
+                        .setMaxBrightness(PowerManager.BRIGHTNESS_MAX)
+                        .setCustomAnimationRate(-1).build());
 
         TestInjector injector = spy(new TestInjector(displayPowerState, animator,
                 automaticBrightnessController, wakelockController, brightnessMappingStrategy,
                 hysteresisLevels, screenOffBrightnessSensorController, hbmController, hdrClamper,
-                flags));
+                clamperController, flags));
 
         final LogicalDisplay display = mock(LogicalDisplay.class);
         final DisplayDevice device = mock(DisplayDevice.class);
@@ -1662,8 +1718,8 @@
 
         return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting,
                 animator, automaticBrightnessController, wakelockController,
-                screenOffBrightnessSensorController, hbmController, hdrClamper, hbmMetadata,
-                brightnessMappingStrategy, injector, config);
+                screenOffBrightnessSensorController, hbmController, hdrClamper, clamperController,
+                hbmMetadata, brightnessMappingStrategy, injector, config);
     }
 
     /**
@@ -1682,6 +1738,7 @@
         public final HighBrightnessModeController hbmController;
 
         public final HdrClamper hdrClamper;
+        public final BrightnessClamperController clamperController;
         public final HighBrightnessModeMetadata hbmMetadata;
         public final BrightnessMappingStrategy brightnessMappingStrategy;
         public final DisplayPowerController2.Injector injector;
@@ -1695,6 +1752,7 @@
                 ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
                 HighBrightnessModeController hbmController,
                 HdrClamper hdrClamper,
+                BrightnessClamperController clamperController,
                 HighBrightnessModeMetadata hbmMetadata,
                 BrightnessMappingStrategy brightnessMappingStrategy,
                 DisplayPowerController2.Injector injector,
@@ -1709,6 +1767,7 @@
             this.screenOffBrightnessSensorController = screenOffBrightnessSensorController;
             this.hbmController = hbmController;
             this.hdrClamper = hdrClamper;
+            this.clamperController = clamperController;
             this.hbmMetadata = hbmMetadata;
             this.brightnessMappingStrategy = brightnessMappingStrategy;
             this.injector = injector;
@@ -1728,6 +1787,8 @@
 
         private final HdrClamper mHdrClamper;
 
+        private final BrightnessClamperController mClamperController;
+
         private final DisplayManagerFlags mFlags;
 
         TestInjector(DisplayPowerState dps, DualRampAnimator<DisplayPowerState> animator,
@@ -1738,6 +1799,7 @@
                 ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
                 HighBrightnessModeController highBrightnessModeController,
                 HdrClamper hdrClamper,
+                BrightnessClamperController clamperController,
                 DisplayManagerFlags flags) {
             mDisplayPowerState = dps;
             mAnimator = animator;
@@ -1748,6 +1810,7 @@
             mScreenOffBrightnessSensorController = screenOffBrightnessSensorController;
             mHighBrightnessModeController = highBrightnessModeController;
             mHdrClamper = hdrClamper;
+            mClamperController = clamperController;
             mFlags = flags;
         }
 
@@ -1864,6 +1927,14 @@
         }
 
         @Override
+        BrightnessClamperController getBrightnessClamperController(Handler handler,
+                BrightnessClamperController.ClamperChangeListener clamperChangeListener,
+                BrightnessClamperController.DisplayDeviceData data, Context context,
+                DisplayManagerFlags flags) {
+            return mClamperController;
+        }
+
+        @Override
         DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
                 SensorManager sensorManager, Resources resources) {
             return mDisplayWhiteBalanceControllerMock;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
index c0e0df9..ff2b1f4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
@@ -137,8 +137,10 @@
         float initialBrightness = 0.8f;
         boolean initialSlowChange = true;
         float clampedBrightness = 0.6f;
+        float customAnimationRate = 0.01f;
         when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
         when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL);
+        when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
         when(mMockClamper.isActive()).thenReturn(false);
         mTestInjector.mCapturedChangeListener.onChanged();
         mTestHandler.flush();
@@ -150,6 +152,7 @@
         assertEquals(PowerManager.BRIGHTNESS_MAX, state.getMaxBrightness(), FLOAT_TOLERANCE);
         assertEquals(0,
                 state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
+        assertEquals(-1, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
         assertEquals(initialSlowChange, state.isSlowChange());
     }
 
@@ -158,8 +161,10 @@
         float initialBrightness = 0.8f;
         boolean initialSlowChange = true;
         float clampedBrightness = 0.6f;
+        float customAnimationRate = 0.01f;
         when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
         when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL);
+        when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
         when(mMockClamper.isActive()).thenReturn(true);
         mTestInjector.mCapturedChangeListener.onChanged();
         mTestHandler.flush();
@@ -171,6 +176,7 @@
         assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
         assertEquals(BrightnessReason.MODIFIER_THROTTLED,
                 state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
+        assertEquals(customAnimationRate, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
         assertFalse(state.isSlowChange());
     }
 
@@ -179,8 +185,10 @@
         float initialBrightness = 0.6f;
         boolean initialSlowChange = true;
         float clampedBrightness = 0.8f;
+        float customAnimationRate = 0.01f;
         when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
         when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL);
+        when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
         when(mMockClamper.isActive()).thenReturn(true);
         mTestInjector.mCapturedChangeListener.onChanged();
         mTestHandler.flush();
@@ -192,6 +200,7 @@
         assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
         assertEquals(BrightnessReason.MODIFIER_THROTTLED,
                 state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
+        assertEquals(customAnimationRate, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
         assertFalse(state.isSlowChange());
     }
 
@@ -200,8 +209,10 @@
         float initialBrightness = 0.8f;
         boolean initialSlowChange = true;
         float clampedBrightness = 0.6f;
+        float customAnimationRate = 0.01f;
         when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
         when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL);
+        when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
         when(mMockClamper.isActive()).thenReturn(true);
         mTestInjector.mCapturedChangeListener.onChanged();
         mTestHandler.flush();
@@ -216,6 +227,7 @@
         assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
         assertEquals(BrightnessReason.MODIFIER_THROTTLED,
                 state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
+        assertEquals(customAnimationRate, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
         assertEquals(initialSlowChange, state.isSlowChange());
     }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
index ee187ba..8d8274c 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
@@ -56,9 +56,9 @@
     private static final HdrBrightnessData TEST_HDR_DATA = new HdrBrightnessData(
             Map.of(500f, 0.6f),
             /* brightnessIncreaseDebounceMillis= */ 1000,
-            /* brightnessIncreaseDurationMillis= */ 2000,
+            /* screenBrightnessRampIncrease= */ 0.02f,
             /* brightnessDecreaseDebounceMillis= */ 3000,
-            /* brightnessDecreaseDurationMillis= */4000
+            /* screenBrightnessRampDecrease= */0.04f
     );
 
     private static final int WIDTH = 600;
@@ -152,8 +152,7 @@
         mClock.fastForward(3000);
         mTestHandler.timeAdvance();
         assertEquals(0.6f, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
-        // 0.6 to HLG = 0.905727, rate = (1-0.905727) / 4
-        assertEquals(0.023568f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
+        assertEquals(0.04, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
     }
 
     @Test
@@ -181,8 +180,7 @@
         mClock.fastForward(1000);
         mTestHandler.timeAdvance();
         assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
-        // 0.6 to HLG = 0.905727, rate = (1-0.905727) / 2
-        assertEquals(0.047137f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
+        assertEquals(0.02f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
     }
 
     @Test
@@ -209,8 +207,7 @@
         mClock.fastForward(3000);
         mTestHandler.timeAdvance();
         assertEquals(0.6f, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
-        // 0.6 to HLG = 0.905727, rate = (1-0.905727) / 4
-        assertEquals(0.023568f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
+        assertEquals(0.04f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
     }
 
     // MsgInfo.sendTime is calculated first by adding SystemClock.uptimeMillis()
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
index 596a3f3..b3605cc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
@@ -280,7 +280,9 @@
                 0, 0);
 
         // Sleep until timeout should have triggered
-        SystemClock.sleep(ActivityManagerService.PROC_START_TIMEOUT + 1000);
+        if (wedge) {
+            SystemClock.sleep(ActivityManagerService.PROC_START_TIMEOUT + 1000);
+        }
 
         return app;
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 410ae35..367e14b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -228,7 +228,7 @@
         LocalServices.removeServiceForTest(AlarmManagerInternal.class);
         LocalServices.addService(AlarmManagerInternal.class, mAlarmManagerInt);
         doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
-        doNothing().when(mPackageManagerInt).setPackageStoppedState(any(), anyBoolean(), anyInt());
+        doNothing().when(mPackageManagerInt).notifyComponentUsed(any(), anyInt(), any(), any());
         doAnswer((invocation) -> {
             return getUidForPackage(invocation.getArgument(0));
         }).when(mPackageManagerInt).getPackageUid(any(), anyLong(), eq(UserHandle.USER_SYSTEM));
@@ -1014,8 +1014,9 @@
                     eq(PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER));
 
             // Confirm that we unstopped manifest receivers
-            verify(mAms.mPackageManagerInt, atLeastOnce()).setPackageStoppedState(
-                    eq(receiverApp.info.packageName), eq(false), eq(UserHandle.USER_SYSTEM));
+            verify(mAms.mPackageManagerInt, atLeastOnce()).notifyComponentUsed(
+                    eq(receiverApp.info.packageName), eq(UserHandle.USER_SYSTEM),
+                    eq(callerApp.info.packageName), any());
         }
 
         // Confirm that we've reported expected usage events
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 76b41b7..c493f84 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -2629,7 +2629,7 @@
                 PROCESS_STATE_NONEXISTENT, PROCESS_STATE_NONEXISTENT,
                 0, 0, false, false, false, ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE,
                 false, false, false, hasShownUi, false, false, false, false, false, false, null,
-                0, 0, 0, true, 0, null, false);
+                0, Long.MIN_VALUE, Long.MIN_VALUE, true, 0, null, false);
     }
 
     private ProcessRecord makeProcessRecord(ActivityManagerService service, int pid, int uid,
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 2f6859c..be33b1b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -165,8 +165,7 @@
             null
         }
         whenever(mocks.settings.addPackageLPw(nullable(), nullable(), nullable(), nullable(),
-                nullable(), nullable(), nullable(), nullable(), nullable(), nullable(), nullable(),
-                nullable(), nullable(), nullable(), nullable(), nullable(), nullable())) {
+                nullable(), nullable(), nullable())) {
             val name: String = getArgument(0)
             val pendingAdd = mPendingPackageAdds.firstOrNull { it.first == name }
                     ?: return@whenever null
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 eb50556..610ea90 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.when;
 
 import android.app.AppOpsManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
@@ -427,6 +428,7 @@
         for (LauncherActivityInfo mainActivity : createLauncherActivities()) {
             ArchiveState.ArchiveActivityInfo activityInfo = new ArchiveState.ArchiveActivityInfo(
                     mainActivity.getLabel().toString(),
+                    mainActivity.getComponentName(),
                     ICON_PATH, null);
             activityInfos.add(activityInfo);
         }
@@ -437,9 +439,11 @@
         ActivityInfo activityInfo = mock(ActivityInfo.class);
         LauncherActivityInfo activity1 = mock(LauncherActivityInfo.class);
         when(activity1.getLabel()).thenReturn("activity1");
+        when(activity1.getComponentName()).thenReturn(new ComponentName("pkg1", "class1"));
         when(activity1.getActivityInfo()).thenReturn(activityInfo);
         LauncherActivityInfo activity2 = mock(LauncherActivityInfo.class);
         when(activity2.getLabel()).thenReturn("activity2");
+        when(activity2.getComponentName()).thenReturn(new ComponentName("pkg2", "class2"));
         when(activity2.getActivityInfo()).thenReturn(activityInfo);
         return List.of(activity1, activity2);
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index eefe5af..3dbab13 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -462,7 +462,7 @@
         wallpaper.wallpaperObserver.stopWatching();
 
         spyOn(wallpaper.wallpaperObserver);
-        doReturn(wallpaper).when(wallpaper.wallpaperObserver).dataForEvent(true, false);
+        doReturn(wallpaper).when(wallpaper.wallpaperObserver).dataForEvent(false);
         wallpaper.wallpaperObserver.onEvent(CLOSE_WRITE, WALLPAPER);
 
         // ACTION_WALLPAPER_CHANGED should be invoked before onWallpaperColorsChanged.
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index cf315a4..b9e45ba 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.accessibility;
 
+import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
@@ -68,6 +69,7 @@
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.provider.Settings;
 import android.testing.TestableContext;
+import android.util.ArraySet;
 import android.view.Display;
 import android.view.DisplayAdjustments;
 import android.view.DisplayInfo;
@@ -600,6 +602,74 @@
     }
 
     @Test
+    public void testPackagesForceStopped_disablesRelevantService() {
+        final AccessibilityServiceInfo info_a = new AccessibilityServiceInfo();
+        info_a.setComponentName(COMPONENT_NAME);
+        final AccessibilityServiceInfo info_b = new AccessibilityServiceInfo();
+        info_b.setComponentName(new ComponentName("package", "class"));
+
+        AccessibilityUserState userState = mA11yms.getCurrentUserState();
+        userState.mInstalledServices.clear();
+        userState.mInstalledServices.add(info_a);
+        userState.mInstalledServices.add(info_b);
+        userState.mEnabledServices.clear();
+        userState.mEnabledServices.add(info_a.getComponentName());
+        userState.mEnabledServices.add(info_b.getComponentName());
+
+        synchronized (mA11yms.getLock()) {
+            mA11yms.onPackagesForceStoppedLocked(
+                    new String[]{info_a.getComponentName().getPackageName()}, userState);
+        }
+
+        //Assert user state change
+        userState = mA11yms.getCurrentUserState();
+        assertThat(userState.mEnabledServices).containsExactly(info_b.getComponentName());
+        //Assert setting change
+        final Set<ComponentName> componentsFromSetting = new ArraySet<>();
+        mA11yms.readComponentNamesFromSettingLocked(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                userState.mUserId, componentsFromSetting);
+        assertThat(componentsFromSetting).containsExactly(info_b.getComponentName());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DISABLE_CONTINUOUS_SHORTCUT_ON_FORCE_STOP)
+    public void testPackagesForceStopped_fromContinuousService_removesButtonTarget() {
+        final AccessibilityServiceInfo info_a = new AccessibilityServiceInfo();
+        info_a.setComponentName(COMPONENT_NAME);
+        info_a.flags = FLAG_REQUEST_ACCESSIBILITY_BUTTON;
+        final AccessibilityServiceInfo info_b = new AccessibilityServiceInfo();
+        info_b.setComponentName(new ComponentName("package", "class"));
+
+        AccessibilityUserState userState = mA11yms.getCurrentUserState();
+        userState.mInstalledServices.clear();
+        userState.mInstalledServices.add(info_a);
+        userState.mInstalledServices.add(info_b);
+        userState.mAccessibilityButtonTargets.clear();
+        userState.mAccessibilityButtonTargets.add(info_a.getComponentName().flattenToString());
+        userState.mAccessibilityButtonTargets.add(info_b.getComponentName().flattenToString());
+
+        // despite force stopping both packages, only the first service has the relevant flag,
+        // so only the first should be removed.
+        synchronized (mA11yms.getLock()) {
+            mA11yms.onPackagesForceStoppedLocked(
+                    new String[]{
+                            info_a.getComponentName().getPackageName(),
+                            info_b.getComponentName().getPackageName()},
+                    userState);
+        }
+
+        //Assert user state change
+        userState = mA11yms.getCurrentUserState();
+        assertThat(userState.mAccessibilityButtonTargets).containsExactly(
+                info_b.getComponentName().flattenToString());
+        //Assert setting change
+        final Set<String> targetsFromSetting = new ArraySet<>();
+        mA11yms.readColonDelimitedSettingToSet(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
+                userState.mUserId, str -> str, targetsFromSetting);
+        assertThat(targetsFromSetting).containsExactly(info_b.getComponentName().flattenToString());
+    }
+
+    @Test
     @RequiresFlagsDisabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK)
     // Test old behavior to validate lock detection for the old (locked access) case.
     public void testPackageMonitorScanPackages_scansWhileHoldingLock() {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
index 63281b7..71007f5 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
@@ -155,7 +155,7 @@
         mUserState.mAccessibilityButtonTargets.add(COMPONENT_NAME.flattenToString());
         mUserState.setTargetAssignedToAccessibilityButton(COMPONENT_NAME.flattenToString());
         mUserState.setTouchExplorationEnabledLocked(true);
-        mUserState.setDisplayMagnificationEnabledLocked(true);
+        mUserState.setMagnificationSingleFingerTripleTapEnabledLocked(true);
         mUserState.setAutoclickEnabledLocked(true);
         mUserState.setUserNonInteractiveUiTimeoutLocked(30);
         mUserState.setUserInteractiveUiTimeoutLocked(30);
@@ -177,7 +177,7 @@
         assertTrue(mUserState.mAccessibilityButtonTargets.isEmpty());
         assertNull(mUserState.getTargetAssignedToAccessibilityButton());
         assertFalse(mUserState.isTouchExplorationEnabledLocked());
-        assertFalse(mUserState.isDisplayMagnificationEnabledLocked());
+        assertFalse(mUserState.isMagnificationSingleFingerTripleTapEnabledLocked());
         assertFalse(mUserState.isAutoclickEnabledLocked());
         assertEquals(0, mUserState.getUserNonInteractiveUiTimeoutLocked());
         assertEquals(0, mUserState.getUserInteractiveUiTimeoutLocked());
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index 6bfd93b..4bb7d63 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -4,6 +4,8 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
+import static com.android.server.job.JobStore.JOB_FILE_SPLIT_PREFIX;
+
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -46,6 +48,7 @@
 import org.junit.runner.RunWith;
 
 import java.io.File;
+import java.nio.file.Files;
 import java.time.Clock;
 import java.time.ZoneOffset;
 import java.util.ArrayList;
@@ -209,6 +212,43 @@
         assertEquals("Incorrect # of persisted tasks.", 0, jobStatusSet.size());
     }
 
+    @Test
+    public void testSkipExtraFiles() throws Exception {
+        setUseSplitFiles(true);
+        final JobInfo task1 = new Builder(8, mComponent)
+                .setRequiresDeviceIdle(true)
+                .setPeriodic(10000L)
+                .setRequiresCharging(true)
+                .setPersisted(true)
+                .build();
+        final JobInfo task2 = new Builder(12, mComponent)
+                .setMinimumLatency(5000L)
+                .setBackoffCriteria(15000L, JobInfo.BACKOFF_POLICY_LINEAR)
+                .setOverrideDeadline(30000L)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
+                .setPersisted(true)
+                .build();
+        final int uid1 = SOME_UID;
+        final int uid2 = uid1 + 1;
+        final JobStatus JobStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null, null);
+        final JobStatus JobStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null, null);
+        runWritingJobsToDisk(JobStatus1, JobStatus2);
+
+        final File rootDir = new File(mTestContext.getFilesDir(), "system/job");
+        final File file1 = new File(rootDir, JOB_FILE_SPLIT_PREFIX + uid1 + ".xml");
+        final File file2 = new File(rootDir, JOB_FILE_SPLIT_PREFIX + uid2 + ".xml");
+
+        Files.copy(file1.toPath(),
+                new File(rootDir, JOB_FILE_SPLIT_PREFIX + uid1 + ".xml.bak").toPath());
+        Files.copy(file1.toPath(), new File(rootDir, "random.xml").toPath());
+        Files.copy(file2.toPath(),
+                new File(rootDir, "blah" + JOB_FILE_SPLIT_PREFIX + uid1 + ".xml").toPath());
+
+        JobSet jobStatusSet = new JobSet();
+        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+        assertEquals("Incorrect # of persisted tasks.", 2, jobStatusSet.size());
+    }
+
     /**
      * Test that dynamic constraints aren't written to disk.
      */
@@ -254,22 +294,22 @@
         file = new File(mTestContext.getFilesDir(), "10000");
         assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
 
-        file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX);
+        file = new File(mTestContext.getFilesDir(), JOB_FILE_SPLIT_PREFIX);
         assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
 
-        file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "text.xml");
+        file = new File(mTestContext.getFilesDir(), JOB_FILE_SPLIT_PREFIX + "text.xml");
         assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
 
-        file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + ".xml");
+        file = new File(mTestContext.getFilesDir(), JOB_FILE_SPLIT_PREFIX + ".xml");
         assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
 
-        file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "-10123.xml");
+        file = new File(mTestContext.getFilesDir(), JOB_FILE_SPLIT_PREFIX + "-10123.xml");
         assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
 
-        file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "1.xml");
+        file = new File(mTestContext.getFilesDir(), JOB_FILE_SPLIT_PREFIX + "1.xml");
         assertEquals(1, JobStore.extractUidFromJobFileName(file));
 
-        file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "101023.xml");
+        file = new File(mTestContext.getFilesDir(), JOB_FILE_SPLIT_PREFIX + "101023.xml");
         assertEquals(101023, JobStore.extractUidFromJobFileName(file));
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 37a6d22..eca19c8 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -255,7 +255,7 @@
     public void testUnlockUserKeyIfUnsecuredPassesPrimaryUserAuthSecret() throws RemoteException {
         initSpAndSetCredential(PRIMARY_USER_ID, newPassword(null));
         reset(mAuthSecretService);
-        mLocalService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
+        mService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
         verify(mAuthSecretService).setPrimaryUserCredential(any(byte[].class));
     }
 
@@ -267,7 +267,7 @@
         mService.setLockCredential(nonePassword(), password, PRIMARY_USER_ID);
 
         reset(mAuthSecretService);
-        mLocalService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
+        mService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
         verify(mAuthSecretService).setPrimaryUserCredential(any(byte[].class));
     }
 
@@ -285,39 +285,39 @@
     @Test
     public void testHeadlessSystemUserDoesNotPassAuthSecret() throws RemoteException {
         setupHeadlessTest();
-        mLocalService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
+        mService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
         verify(mAuthSecretService, never()).setPrimaryUserCredential(any(byte[].class));
     }
 
     @Test
     public void testHeadlessSecondaryUserPassesAuthSecret() throws RemoteException {
         setupHeadlessTest();
-        mLocalService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
+        mService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
         verify(mAuthSecretService).setPrimaryUserCredential(any(byte[].class));
     }
 
     @Test
     public void testHeadlessTertiaryUserPassesSameAuthSecret() throws RemoteException {
         setupHeadlessTest();
-        mLocalService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
+        mService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
         var captor = ArgumentCaptor.forClass(byte[].class);
         verify(mAuthSecretService).setPrimaryUserCredential(captor.capture());
         var value = captor.getValue();
         reset(mAuthSecretService);
-        mLocalService.unlockUserKeyIfUnsecured(TERTIARY_USER_ID);
+        mService.unlockUserKeyIfUnsecured(TERTIARY_USER_ID);
         verify(mAuthSecretService).setPrimaryUserCredential(eq(value));
     }
 
     @Test
     public void testHeadlessTertiaryUserPassesSameAuthSecretAfterReset() throws RemoteException {
         setupHeadlessTest();
-        mLocalService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
+        mService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
         var captor = ArgumentCaptor.forClass(byte[].class);
         verify(mAuthSecretService).setPrimaryUserCredential(captor.capture());
         var value = captor.getValue();
         mService.clearAuthSecret();
         reset(mAuthSecretService);
-        mLocalService.unlockUserKeyIfUnsecured(TERTIARY_USER_ID);
+        mService.unlockUserKeyIfUnsecured(TERTIARY_USER_ID);
         verify(mAuthSecretService).setPrimaryUserCredential(eq(value));
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index f94aff7..9f6d7f2 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -27,6 +27,7 @@
 import static android.view.Display.INVALID_DISPLAY;
 
 import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -39,6 +40,7 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 import static org.testng.Assert.assertThrows;
 
@@ -133,7 +135,7 @@
     private final MediaProjectionManagerService.Injector mMediaProjectionMetricsLoggerInjector =
             new MediaProjectionManagerService.Injector() {
                 @Override
-                MediaProjectionMetricsLogger mediaProjectionMetricsLogger() {
+                MediaProjectionMetricsLogger mediaProjectionMetricsLogger(Context context) {
                     return mMediaProjectionMetricsLogger;
                 }
             };
@@ -311,6 +313,70 @@
     }
 
     @Test
+    public void stop_noActiveProjections_doesNotLog() throws Exception {
+        MediaProjectionManagerService service =
+                new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+        MediaProjectionManagerService.MediaProjection projection =
+                startProjectionPreconditions(service);
+
+        projection.stop();
+
+        verifyZeroInteractions(mMediaProjectionMetricsLogger);
+    }
+
+    @Test
+    public void stop_noSession_logsHostUidAndUnknownTargetUid() throws Exception {
+        MediaProjectionManagerService service =
+                new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+        MediaProjectionManagerService.MediaProjection projection =
+                startProjectionPreconditions(service);
+        projection.start(mIMediaProjectionCallback);
+
+        projection.stop();
+
+        verify(mMediaProjectionMetricsLogger)
+                .logStopped(UID, ContentRecordingSession.TARGET_UID_UNKNOWN);
+    }
+
+    @Test
+    public void stop_displaySession_logsHostUidAndUnknownTargetUidFullScreen() throws Exception {
+        MediaProjectionManagerService service =
+                new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+        MediaProjectionManagerService.MediaProjection projection =
+                startProjectionPreconditions(service);
+        projection.start(mIMediaProjectionCallback);
+        doReturn(true)
+                .when(mWindowManagerInternal)
+                .setContentRecordingSession(any(ContentRecordingSession.class));
+        service.setContentRecordingSession(DISPLAY_SESSION);
+
+        projection.stop();
+
+        verify(mMediaProjectionMetricsLogger)
+                .logStopped(UID, ContentRecordingSession.TARGET_UID_FULL_SCREEN);
+    }
+
+    @Test
+    public void stop_taskSession_logsHostUidAndTargetUid() throws Exception {
+        int targetUid = 1234;
+        MediaProjectionManagerService service =
+                new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+        MediaProjectionManagerService.MediaProjection projection =
+                startProjectionPreconditions(service);
+        projection.start(mIMediaProjectionCallback);
+        doReturn(true)
+                .when(mWindowManagerInternal)
+                .setContentRecordingSession(any(ContentRecordingSession.class));
+        ContentRecordingSession taskSession =
+                ContentRecordingSession.createTaskSession(mock(IBinder.class), targetUid);
+        service.setContentRecordingSession(taskSession);
+
+        projection.stop();
+
+        verify(mMediaProjectionMetricsLogger).logStopped(UID, targetUid);
+    }
+
+    @Test
     public void testIsValid_multipleStarts_preventionDisabled() throws NameNotFoundException {
         MediaProjectionManagerService service = new MediaProjectionManagerService(mContext,
                 mPreventReusedTokenDisabledInjector);
@@ -586,6 +652,18 @@
                 /* isSetSessionSuccessful= */ false, RECORD_CANCEL);
     }
 
+    @Test
+    public void notifyPermissionRequestInitiated_forwardsToLogger() {
+        int hostUid = 123;
+        int sessionCreationSource = 456;
+        mService =
+                new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+
+        mService.notifyPermissionRequestInitiated(hostUid, sessionCreationSource);
+
+        verify(mMediaProjectionMetricsLogger).logInitiated(hostUid, sessionCreationSource);
+    }
+
     /**
      * Executes and validates scenario where the consent result indicates the projection ends.
      */
@@ -749,8 +827,10 @@
     public void setContentRecordingSession_success_logsCaptureInProgress()
             throws Exception {
         mService.addCallback(mWatcherCallback);
-        MediaProjectionManagerService service = new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
-        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+        MediaProjectionManagerService service =
+                new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+        MediaProjectionManagerService.MediaProjection projection =
+                startProjectionPreconditions(service);
         projection.start(mIMediaProjectionCallback);
         doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
                 any(ContentRecordingSession.class));
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java
new file mode 100644
index 0000000..73b4cc8
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media.projection;
+
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.time.Duration;
+
+/**
+ * Tests for the {@link MediaProjectionMetricsLoggerTest} class.
+ *
+ * <p>Build/Install/Run: atest FrameworksServicesTests:MediaProjectionMetricsLoggerTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class MediaProjectionMetricsLoggerTest {
+
+    private static final int TEST_HOST_UID = 123;
+    private static final int TEST_TARGET_UID = 456;
+    private static final int TEST_CREATION_SOURCE = 789;
+
+    @Mock private FrameworkStatsLogWrapper mFrameworkStatsLogWrapper;
+    @Mock private MediaProjectionSessionIdGenerator mSessionIdGenerator;
+    @Mock private MediaProjectionTimestampStore mTimestampStore;
+
+    private MediaProjectionMetricsLogger mLogger;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mLogger =
+                new MediaProjectionMetricsLogger(
+                        mFrameworkStatsLogWrapper, mSessionIdGenerator, mTimestampStore);
+    }
+
+    @Test
+    public void logInitiated_logsStateChangedAtomId() {
+        mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+
+        verifyStateChangedAtomIdLogged();
+    }
+
+    @Test
+    public void logInitiated_logsStateInitiated() {
+        mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+
+        verifyStateLogged(MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED);
+    }
+
+    @Test
+    public void logInitiated_logsHostUid() {
+        mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+
+        verifyHostUidLogged(TEST_HOST_UID);
+    }
+
+    @Test
+    public void logInitiated_logsSessionCreationSource() {
+        mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+
+        verifyCreationSourceLogged(TEST_CREATION_SOURCE);
+    }
+
+    @Test
+    public void logInitiated_logsUnknownTargetUid() {
+        mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+
+        verifyTargetUidLogged(-2);
+    }
+
+    @Test
+    public void logInitiated_noPreviousSession_logsUnknownTimeSinceLastActive() {
+        when(mTimestampStore.timeSinceLastActiveSession()).thenReturn(null);
+
+        mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+
+        verifyTimeSinceLastActiveSessionLogged(-1);
+    }
+
+    @Test
+    public void logInitiated_previousSession_logsTimeSinceLastActiveInSeconds() {
+        Duration timeSinceLastActiveSession = Duration.ofHours(1234);
+        when(mTimestampStore.timeSinceLastActiveSession()).thenReturn(timeSinceLastActiveSession);
+
+        mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+
+        verifyTimeSinceLastActiveSessionLogged((int) timeSinceLastActiveSession.toSeconds());
+    }
+
+    @Test
+    public void logInitiated_logsNewSessionId() {
+        int newSessionId = 123;
+        when(mSessionIdGenerator.createAndGetNewSessionId()).thenReturn(newSessionId);
+
+        mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+
+        verifySessionIdLogged(newSessionId);
+    }
+
+    @Test
+    public void logInitiated_logsPreviousState() {
+        mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+        verifyPreviousStateLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN);
+
+        mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+        verifyPreviousStateLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED);
+    }
+
+    @Test
+    public void logStopped_logsStateChangedAtomId() {
+        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+
+        verifyStateChangedAtomIdLogged();
+    }
+
+    @Test
+    public void logStopped_logsCurrentSessionId() {
+        int currentSessionId = 987;
+        when(mSessionIdGenerator.getCurrentSessionId()).thenReturn(currentSessionId);
+
+        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+
+        verifySessionIdLogged(currentSessionId);
+    }
+
+    @Test
+    public void logStopped_logsStateStopped() {
+        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+
+        verifyStateLogged(MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED);
+    }
+
+    @Test
+    public void logStopped_logsHostUid() {
+        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+
+        verifyHostUidLogged(TEST_HOST_UID);
+    }
+
+    @Test
+    public void logStopped_logsTargetUid() {
+        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+
+        verifyTargetUidLogged(TEST_TARGET_UID);
+    }
+
+    @Test
+    public void logStopped_logsUnknownTimeSinceLastActive() {
+        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+
+        verifyTimeSinceLastActiveSessionLogged(-1);
+    }
+
+    @Test
+    public void logStopped_logsUnknownSessionCreationSource() {
+        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+
+        verifyCreationSourceLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+    }
+
+    @Test
+    public void logStopped_logsPreviousState() {
+        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+        verifyPreviousStateLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN);
+
+        mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+        verifyPreviousStateLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED);
+
+        mLogger.logStopped(TEST_HOST_UID, TEST_CREATION_SOURCE);
+        verifyPreviousStateLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED);
+    }
+
+    @Test
+    public void logStopped_registersActiveSessionEnded_afterLogging() {
+        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+
+        InOrder inOrder = inOrder(mFrameworkStatsLogWrapper, mTimestampStore);
+        inOrder.verify(mFrameworkStatsLogWrapper)
+                .write(
+                        /* code= */ anyInt(),
+                        /* sessionId= */ anyInt(),
+                        /* state= */ anyInt(),
+                        /* previousState= */ anyInt(),
+                        /* hostUid= */ anyInt(),
+                        /* targetUid= */ anyInt(),
+                        /* timeSinceLastActive= */ anyInt(),
+                        /* creationSource= */ anyInt());
+        inOrder.verify(mTimestampStore).registerActiveSessionEnded();
+    }
+
+    private void verifyStateChangedAtomIdLogged() {
+        verify(mFrameworkStatsLogWrapper)
+                .write(
+                        /* code= */ eq(FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED),
+                        /* sessionId= */ anyInt(),
+                        /* state= */ anyInt(),
+                        /* previousState= */ anyInt(),
+                        /* hostUid= */ anyInt(),
+                        /* targetUid= */ anyInt(),
+                        /* timeSinceLastActive= */ anyInt(),
+                        /* creationSource= */ anyInt());
+    }
+
+    private void verifyStateLogged(int state) {
+        verify(mFrameworkStatsLogWrapper)
+                .write(
+                        /* code= */ anyInt(),
+                        /* sessionId= */ anyInt(),
+                        eq(state),
+                        /* previousState= */ anyInt(),
+                        /* hostUid= */ anyInt(),
+                        /* targetUid= */ anyInt(),
+                        /* timeSinceLastActive= */ anyInt(),
+                        /* creationSource= */ anyInt());
+    }
+
+    private void verifyHostUidLogged(int hostUid) {
+        verify(mFrameworkStatsLogWrapper)
+                .write(
+                        /* code= */ anyInt(),
+                        /* sessionId= */ anyInt(),
+                        /* state= */ anyInt(),
+                        /* previousState= */ anyInt(),
+                        eq(hostUid),
+                        /* targetUid= */ anyInt(),
+                        /* timeSinceLastActive= */ anyInt(),
+                        /* creationSource= */ anyInt());
+    }
+
+    private void verifyCreationSourceLogged(int creationSource) {
+        verify(mFrameworkStatsLogWrapper)
+                .write(
+                        /* code= */ anyInt(),
+                        /* sessionId= */ anyInt(),
+                        /* state= */ anyInt(),
+                        /* previousState= */ anyInt(),
+                        /* hostUid= */ anyInt(),
+                        /* targetUid= */ anyInt(),
+                        /* timeSinceLastActive= */ anyInt(),
+                        eq(creationSource));
+    }
+
+    private void verifyTargetUidLogged(int targetUid) {
+        verify(mFrameworkStatsLogWrapper)
+                .write(
+                        /* code= */ anyInt(),
+                        /* sessionId= */ anyInt(),
+                        /* state= */ anyInt(),
+                        /* previousState= */ anyInt(),
+                        /* hostUid= */ anyInt(),
+                        eq(targetUid),
+                        /* timeSinceLastActive= */ anyInt(),
+                        /* creationSource= */ anyInt());
+    }
+
+    private void verifyTimeSinceLastActiveSessionLogged(int timeSinceLastActiveSession) {
+        verify(mFrameworkStatsLogWrapper)
+                .write(
+                        /* code= */ anyInt(),
+                        /* sessionId= */ anyInt(),
+                        /* state= */ anyInt(),
+                        /* previousState= */ anyInt(),
+                        /* hostUid= */ anyInt(),
+                        /* targetUid= */ anyInt(),
+                        /* timeSinceLastActive= */ eq(timeSinceLastActiveSession),
+                        /* creationSource= */ anyInt());
+    }
+
+    private void verifySessionIdLogged(int newSessionId) {
+        verify(mFrameworkStatsLogWrapper)
+                .write(
+                        /* code= */ anyInt(),
+                        /* sessionId= */ eq(newSessionId),
+                        /* state= */ anyInt(),
+                        /* previousState= */ anyInt(),
+                        /* hostUid= */ anyInt(),
+                        /* targetUid= */ anyInt(),
+                        /* timeSinceLastActive= */ anyInt(),
+                        /* creationSource= */ anyInt());
+    }
+
+    private void verifyPreviousStateLogged(int previousState) {
+        verify(mFrameworkStatsLogWrapper)
+                .write(
+                        /* code= */ anyInt(),
+                        /* sessionId= */ anyInt(),
+                        /* state= */ anyInt(),
+                        eq(previousState),
+                        /* hostUid= */ anyInt(),
+                        /* targetUid= */ anyInt(),
+                        /* timeSinceLastActive= */ anyInt(),
+                        /* creationSource= */ anyInt());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionTimestampStoreTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionTimestampStoreTest.java
new file mode 100644
index 0000000..7723541
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionTimestampStoreTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media.projection;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.InstantSource;
+
+/**
+ * Tests for the {@link MediaProjectionTimestampStore} class.
+ *
+ * <p>Build/Install/Run: atest FrameworksServicesTests:MediaProjectionTimestampStoreTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class MediaProjectionTimestampStoreTest {
+
+    private static final String TEST_PREFS_FILE = "media-projection-timestamp-test";
+
+    private final Context mContext =
+            InstrumentationRegistry.getInstrumentation().getTargetContext();
+    private final File mSharedPreferencesFile = new File(mContext.getCacheDir(), TEST_PREFS_FILE);
+    private final SharedPreferences mSharedPreferences = createSharePreferences();
+
+    private Instant mCurrentInstant = Instant.ofEpochMilli(0);
+
+    private final InstantSource mInstantSource = () -> mCurrentInstant;
+    private final MediaProjectionTimestampStore mStore =
+            new MediaProjectionTimestampStore(mSharedPreferences, mInstantSource);
+
+    @Before
+    public void setUp() {
+        mSharedPreferences.edit().clear().commit();
+    }
+
+    @After
+    public void tearDown() {
+        mSharedPreferences.edit().clear().commit();
+        mSharedPreferencesFile.delete();
+    }
+
+    @Test
+    public void timeSinceLastActiveSession_byDefault_returnsNull() {
+        assertThat(mStore.timeSinceLastActiveSession()).isNull();
+    }
+
+    @Test
+    public void timeSinceLastActiveSession_returnsBasedOnLastActiveSessionEnded() {
+        mCurrentInstant = Instant.ofEpochMilli(0);
+        mStore.registerActiveSessionEnded();
+
+        mCurrentInstant = mCurrentInstant.plusSeconds(60);
+
+        assertThat(mStore.timeSinceLastActiveSession()).isEqualTo(Duration.ofSeconds(60));
+    }
+
+    @Test
+    public void timeSinceLastActiveSession_valueIsPersisted() {
+        mCurrentInstant = Instant.ofEpochMilli(0);
+        mStore.registerActiveSessionEnded();
+
+        MediaProjectionTimestampStore newStoreInstance =
+                new MediaProjectionTimestampStore(createSharePreferences(), mInstantSource);
+        mCurrentInstant = mCurrentInstant.plusSeconds(123);
+
+        assertThat(newStoreInstance.timeSinceLastActiveSession())
+                .isEqualTo(Duration.ofSeconds(123));
+    }
+
+    private SharedPreferences createSharePreferences() {
+        return mContext.getSharedPreferences(mSharedPreferencesFile, Context.MODE_PRIVATE);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
index 42be3d3..5d6f36c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
@@ -19,7 +19,6 @@
 import android.content.pm.SigningDetails;
 import android.util.SparseArray;
 
-import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageUserStateImpl;
 
@@ -159,17 +158,19 @@
 
     public PackageSetting build() {
         final PackageSetting packageSetting = new PackageSetting(mName, mRealName,
-                new File(mCodePath), mLegacyNativeLibraryPathString, mPrimaryCpuAbiString,
-                mSecondaryCpuAbiString, mCpuAbiOverrideString, mPVersionCode, mPkgFlags,
-                mPrivateFlags, mSharedUserId, null /* usesSdkLibraries */,
-                null /* usesSdkLibrariesVersions */, null /* usesStaticLibraries */,
-                null  /* usesStaticLibrariesVersions */, mMimeGroups, mDomainSetId);
-        packageSetting.setSignatures(mSigningDetails != null
-                ? new PackageSignatures(mSigningDetails)
-                : new PackageSignatures());
-        packageSetting.setPkg((ParsedPackage) mPkg);
-        packageSetting.setAppId(mAppId);
-        packageSetting.setVolumeUuid(this.mVolumeUuid);
+                new File(mCodePath), mPkgFlags, mPrivateFlags, mDomainSetId)
+                .setLegacyNativeLibraryPath(mLegacyNativeLibraryPathString)
+                .setPrimaryCpuAbi(mPrimaryCpuAbiString)
+                .setSecondaryCpuAbi(mSecondaryCpuAbiString)
+                .setCpuAbiOverride(mCpuAbiOverrideString)
+                .setLongVersionCode(mPVersionCode)
+                .setSharedUserAppId(mSharedUserId)
+                .setMimeGroups(mMimeGroups)
+                .setSignatures(mSigningDetails != null
+                        ? new PackageSignatures(mSigningDetails) : new PackageSignatures())
+                .setPkg(mPkg)
+                .setAppId(mAppId)
+                .setVolumeUuid(this.mVolumeUuid);
         if (mInstallSource != null) {
             packageSetting.setInstallSource(mInstallSource);
         }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index 9bd938f..cf8548c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -80,7 +80,9 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IAccessibilityManager;
 import android.view.accessibility.IAccessibilityManagerClient;
+
 import androidx.test.runner.AndroidJUnit4;
+
 import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags;
 import com.android.internal.config.sysui.TestableFlagResolver;
 import com.android.internal.logging.InstanceIdSequence;
@@ -93,6 +95,7 @@
 
 import java.util.List;
 import java.util.Objects;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -190,7 +193,7 @@
         assertTrue(mAccessibilityManager.isEnabled());
 
         // TODO (b/291907312): remove feature flag
-        mTestFlagResolver.setFlagOverride(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR, true);
+        mSetFlagsRule.enableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER);
         // Disable feature flags by default. Tests should enable as needed.
         mSetFlagsRule.disableFlags(Flags.FLAG_POLITE_NOTIFICATIONS, Flags.FLAG_EXPIRE_BITMAPS);
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 3d4b4a6..6792cfe 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -71,6 +71,7 @@
 import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
 import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
 import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
+import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
 import static android.service.notification.Adjustment.KEY_IMPORTANCE;
 import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
@@ -84,7 +85,6 @@
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 
-import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR;
 import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE;
 import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
@@ -207,6 +207,7 @@
 import android.os.UserManager;
 import android.os.WorkSource;
 import android.permission.PermissionManager;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.DeviceConfig;
 import android.provider.MediaStore;
 import android.provider.Settings;
@@ -318,6 +319,7 @@
     private static final int UID_HEADLESS = 1_000_000;
     private static final int TOAST_DURATION = 2_000;
     private static final int SECONDARY_DISPLAY_ID = 42;
+    private static final int TEST_PROFILE_USERHANDLE = 12;
 
     private final int mUid = Binder.getCallingUid();
     private final @UserIdInt int mUserId = UserHandle.getUserId(mUid);
@@ -445,7 +447,7 @@
     TestableNotificationManagerService.StrongAuthTrackerFake mStrongAuthTracker;
 
     TestableFlagResolver mTestFlagResolver = new TestableFlagResolver();
-
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
     private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake(
             1 << 30);
     @Mock
@@ -611,7 +613,8 @@
                 });
 
         // TODO (b/291907312): remove feature flag
-        mTestFlagResolver.setFlagOverride(ENABLE_ATTENTION_HELPER_REFACTOR, false);
+        mSetFlagsRule.disableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER,
+                Flags.FLAG_POLITE_NOTIFICATIONS);
         initNMS();
     }
 
@@ -652,7 +655,7 @@
         verify(mHistoryManager).onBootPhaseAppsCanStart();
 
         // TODO b/291907312: remove feature flag
-        if (mTestFlagResolver.isEnabled(ENABLE_ATTENTION_HELPER_REFACTOR)) {
+        if (Flags.refactorAttentionHelper()) {
             mService.mAttentionHelper.setAudioManager(mAudioManager);
         } else {
             mService.setAudioManager(mAudioManager);
@@ -826,6 +829,12 @@
         mPackageIntentReceiver.onReceive(getContext(), intent);
     }
 
+    private void simulateProfileAvailabilityActions(String intentAction) {
+        final Intent intent = new Intent(intentAction);
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, TEST_PROFILE_USERHANDLE);
+        mUserSwitchIntentReceiver.onReceive(mContext, intent);
+    }
+
     private ArrayMap<Boolean, ArrayList<ComponentName>> generateResetComponentValues() {
         ArrayMap<Boolean, ArrayList<ComponentName>> changed = new ArrayMap<>();
         changed.put(true, new ArrayList<>());
@@ -1683,7 +1692,7 @@
     @Test
     public void testEnqueueNotificationWithTag_WritesExpectedLogs_NAHRefactor() throws Exception {
         // TODO b/291907312: remove feature flag
-        mTestFlagResolver.setFlagOverride(ENABLE_ATTENTION_HELPER_REFACTOR, true);
+        mSetFlagsRule.enableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER);
         // Cleanup NMS before re-initializing
         if (mService != null) {
             try {
@@ -7769,6 +7778,74 @@
         assertEquals(NotificationManagerService.MAX_PACKAGE_TOASTS, mService.mToastQueue.size());
     }
 
+    @Test
+    public void testPrioritizeSystemToasts() throws Exception {
+        // Insert non-system toasts
+        final String testPackage = "testPackageName";
+        assertEquals(0, mService.mToastQueue.size());
+        mService.isSystemUid = false;
+        mService.isSystemAppId = false;
+        setToastRateIsWithinQuota(true);
+        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
+
+        // package is not suspended
+        when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
+                .thenReturn(false);
+
+        INotificationManager nmService = (INotificationManager) mService.mService;
+
+        // Enqueue maximum number of toasts for test package
+        for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_TOASTS; i++) {
+            enqueueTextToast(testPackage, "Text");
+        }
+
+        // Enqueue system toast
+        final String testPackageSystem = "testPackageNameSystem";
+        mService.isSystemUid = true;
+        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackageSystem, false);
+        when(mPackageManager.isPackageSuspendedForUser(testPackageSystem, mUserId))
+                .thenReturn(false);
+
+        enqueueToast(testPackageSystem, new TestableToastCallback());
+
+        // System toast is inserted at the front of the queue, behind current showing toast
+        assertEquals(testPackageSystem, mService.mToastQueue.get(1).pkg);
+    }
+
+    @Test
+    public void testPrioritizeSystemToasts_enqueueAfterExistingSystemToast() throws Exception {
+        // Insert system toasts
+        final String testPackageSystem1 = "testPackageNameSystem1";
+        assertEquals(0, mService.mToastQueue.size());
+        mService.isSystemUid = true;
+        setToastRateIsWithinQuota(true);
+        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackageSystem1, false);
+
+        // package is not suspended
+        when(mPackageManager.isPackageSuspendedForUser(testPackageSystem1, mUserId))
+                .thenReturn(false);
+
+        INotificationManager nmService = (INotificationManager) mService.mService;
+
+        // Enqueue maximum number of toasts for test package
+        for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_TOASTS; i++) {
+            enqueueTextToast(testPackageSystem1, "Text");
+        }
+
+        // Enqueue another system toast
+        final String testPackageSystem2 = "testPackageNameSystem2";
+        mService.isSystemUid = true;
+        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackageSystem2, false);
+        when(mPackageManager.isPackageSuspendedForUser(testPackageSystem2, mUserId))
+                .thenReturn(false);
+
+        enqueueToast(testPackageSystem2, new TestableToastCallback());
+
+        // System toast is inserted at the back of the queue, after the other system toasts
+        assertEquals(testPackageSystem2,
+                mService.mToastQueue.get(mService.mToastQueue.size() - 1).pkg);
+    }
+
     private void setAppInForegroundForToasts(int uid, boolean inForeground) {
         int importance = (inForeground) ? IMPORTANCE_FOREGROUND : IMPORTANCE_NONE;
         when(mActivityManager.getUidImportance(mUid)).thenReturn(importance);
@@ -9078,7 +9155,7 @@
     public void testOnBubbleMetadataChangedToSuppressNotification_soundStopped_NAHRefactor()
         throws Exception {
         // TODO b/291907312: remove feature flag
-        mTestFlagResolver.setFlagOverride(ENABLE_ATTENTION_HELPER_REFACTOR, true);
+        mSetFlagsRule.enableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER);
         // Cleanup NMS before re-initializing
         if (mService != null) {
             try {
@@ -12683,6 +12760,23 @@
         verify(service, times(1)).setDNDMigrationDone(user.id);
     }
 
+    @Test
+    public void testProfileUnavailableIntent() throws RemoteException {
+        mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+        simulateProfileAvailabilityActions(Intent.ACTION_PROFILE_UNAVAILABLE);
+        verify(mWorkerHandler).post(any(Runnable.class));
+        verify(mSnoozeHelper).clearData(anyInt());
+    }
+
+
+    @Test
+    public void testManagedProfileUnavailableIntent() throws RemoteException {
+        mSetFlagsRule.disableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+        simulateProfileAvailabilityActions(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
+        verify(mWorkerHandler).post(any(Runnable.class));
+        verify(mSnoozeHelper).clearData(anyInt());
+    }
+
     private NotificationRecord createAndPostNotification(Notification.Builder nb, String testName)
             throws RemoteException {
         StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, testName, mUid, 0,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
index 121e296..337dd22 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
@@ -91,7 +91,6 @@
     private static final Multimap<Class<?>, String> KNOWN_BAD =
             ImmutableMultimap.<Class<?>, String>builder()
                     .put(Person.Builder.class, "setUri") // TODO: b/281044385
-                    .put(RemoteViews.class, "setRemoteAdapter") // TODO: b/281044385
                     .build();
 
     // Types that we can't really produce. No methods receiving these parameters will be invoked.
diff --git a/services/tests/vibrator/Android.bp b/services/tests/vibrator/Android.bp
index 9544106..6f37967 100644
--- a/services/tests/vibrator/Android.bp
+++ b/services/tests/vibrator/Android.bp
@@ -35,6 +35,7 @@
         "platform-test-annotations",
         "service-permission.stubs.system_server",
         "services.core",
+        "flag-junit",
     ],
 
     platform_apis: true,
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
index 0003555..3d0dca0 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
@@ -16,20 +16,24 @@
 
 package com.android.server.vibrator;
 
+import static android.os.VibrationAttributes.CATEGORY_KEYBOARD;
 import static android.os.VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
 import static android.os.VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF;
+import static android.os.VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE;
 import static android.os.VibrationEffect.Composition.PRIMITIVE_CLICK;
 import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK;
+import static android.os.VibrationEffect.EFFECT_CLICK;
 import static android.os.VibrationEffect.EFFECT_TEXTURE_TICK;
 import static android.os.VibrationEffect.EFFECT_TICK;
 import static android.view.HapticFeedbackConstants.CLOCK_TICK;
 import static android.view.HapticFeedbackConstants.CONTEXT_CLICK;
+import static android.view.HapticFeedbackConstants.KEYBOARD_RELEASE;
+import static android.view.HapticFeedbackConstants.KEYBOARD_TAP;
 import static android.view.HapticFeedbackConstants.SAFE_MODE_ENABLED;
-import static android.view.HapticFeedbackConstants.TEXT_HANDLE_MOVE;
 import static android.view.HapticFeedbackConstants.SCROLL_ITEM_FOCUS;
 import static android.view.HapticFeedbackConstants.SCROLL_LIMIT;
 import static android.view.HapticFeedbackConstants.SCROLL_TICK;
-
+import static android.view.HapticFeedbackConstants.TEXT_HANDLE_MOVE;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -42,9 +46,10 @@
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.VibratorInfo;
+import android.os.vibrator.Flags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.AtomicFile;
 import android.util.SparseArray;
-import android.view.flags.FeatureFlags;
 
 import androidx.test.InstrumentationRegistry;
 
@@ -62,6 +67,8 @@
 public class HapticFeedbackVibrationProviderTest {
     @Rule public MockitoRule rule = MockitoJUnit.rule();
 
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private static final VibrationEffect PRIMITIVE_TICK_EFFECT =
             VibrationEffect.startComposition().addPrimitive(PRIMITIVE_TICK, 0.2497f).compose();
     private static final VibrationEffect PRIMITIVE_CLICK_EFFECT =
@@ -69,11 +76,15 @@
 
     private static final int[] SCROLL_FEEDBACK_CONSTANTS =
             new int[] {SCROLL_ITEM_FOCUS, SCROLL_LIMIT, SCROLL_TICK};
+    private static final int[] KEYBOARD_FEEDBACK_CONSTANTS =
+            new int[] {KEYBOARD_TAP, KEYBOARD_RELEASE};
+
+    private static final float KEYBOARD_VIBRATION_FIXED_AMPLITUDE = 0.62f;
+
     private Context mContext = InstrumentationRegistry.getContext();
     private VibratorInfo mVibratorInfo = VibratorInfo.EMPTY_VIBRATOR_INFO;
 
     @Mock private Resources mResourcesMock;
-    @Mock private FeatureFlags mViewFeatureFlags;
 
     @Test
     public void testNonExistentCustomization_useDefault() throws Exception {
@@ -214,6 +225,62 @@
     }
 
     @Test
+    public void testKeyboardHaptic_noFixedAmplitude_defaultVibrationReturned() {
+        mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK);
+        SparseArray<VibrationEffect> customizations = new SparseArray<>();
+        customizations.put(KEYBOARD_TAP, PRIMITIVE_CLICK_EFFECT);
+        customizations.put(KEYBOARD_RELEASE, PRIMITIVE_TICK_EFFECT);
+
+        // Test with a customization available for `KEYBOARD_TAP` & `KEYBOARD_RELEASE`.
+        HapticFeedbackVibrationProvider hapticProvider = createProvider(customizations);
+
+        assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_TAP))
+                .isEqualTo(PRIMITIVE_CLICK_EFFECT);
+        assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_RELEASE))
+                .isEqualTo(PRIMITIVE_TICK_EFFECT);
+
+        // Test with no customization available for `KEYBOARD_TAP` & `KEYBOARD_RELEASE`.
+        hapticProvider = createProviderWithDefaultCustomizations();
+
+        assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_TAP))
+                .isEqualTo(VibrationEffect.get(EFFECT_CLICK, true /* fallback */));
+        assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_RELEASE))
+                .isEqualTo(VibrationEffect.get(EFFECT_TICK, false /* fallback */));
+    }
+
+    @Test
+    public void testKeyboardHaptic_fixAmplitude_keyboardCategoryOff_defaultVibrationReturned() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
+        mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK);
+        mockKeyboardVibrationFixedAmplitude(KEYBOARD_VIBRATION_FIXED_AMPLITUDE);
+
+        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+
+        assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_TAP))
+                .isEqualTo(VibrationEffect.get(EFFECT_CLICK, true /* fallback */));
+        assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_RELEASE))
+                .isEqualTo(VibrationEffect.get(EFFECT_TICK, false /* fallback */));
+    }
+
+    @Test
+    public void testKeyboardHaptic_fixAmplitude_keyboardCategoryOn_keyboardVibrationReturned() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
+        mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK);
+        mockKeyboardVibrationFixedAmplitude(KEYBOARD_VIBRATION_FIXED_AMPLITUDE);
+
+        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+
+        assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_TAP))
+                .isEqualTo(VibrationEffect.startComposition()
+                        .addPrimitive(PRIMITIVE_CLICK, KEYBOARD_VIBRATION_FIXED_AMPLITUDE)
+                        .compose());
+        assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_RELEASE))
+                .isEqualTo(VibrationEffect.startComposition()
+                        .addPrimitive(PRIMITIVE_TICK, KEYBOARD_VIBRATION_FIXED_AMPLITUDE)
+                        .compose());
+    }
+
+    @Test
     public void testVibrationAttribute_forNotBypassingIntensitySettings() {
         HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
 
@@ -235,7 +302,7 @@
 
     @Test
     public void testVibrationAttribute_scrollFeedback_scrollApiFlagOn_bypassInterruptPolicy() {
-        when(mViewFeatureFlags.scrollFeedbackApi()).thenReturn(true);
+        mSetFlagsRule.enableFlags(android.view.flags.Flags.FLAG_SCROLL_FEEDBACK_API);
         HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
 
         for (int effectId : SCROLL_FEEDBACK_CONSTANTS) {
@@ -248,7 +315,7 @@
 
     @Test
     public void testVibrationAttribute_scrollFeedback_scrollApiFlagOff_noBypassInterruptPolicy() {
-        when(mViewFeatureFlags.scrollFeedbackApi()).thenReturn(false);
+        mSetFlagsRule.disableFlags(android.view.flags.Flags.FLAG_SCROLL_FEEDBACK_API);
         HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
 
         for (int effectId : SCROLL_FEEDBACK_CONSTANTS) {
@@ -259,14 +326,71 @@
         }
     }
 
+    @Test
+    public void testVibrationAttribute_keyboardCategoryOff_notUseKeyboardCategory() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
+        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+
+        for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
+            VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+                    effectId, /* bypassVibrationIntensitySetting= */ false);
+            assertWithMessage("Expected no CATEGORY_KEYBOARD for effect " + effectId)
+                    .that(attrs.getCategory()).isEqualTo(0);
+        }
+    }
+
+    @Test
+    public void testVibrationAttribute_keyboardCategoryOn_useKeyboardCategory() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
+        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+
+        for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
+            VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+                    effectId, /* bypassVibrationIntensitySetting= */ false);
+            assertWithMessage("Expected CATEGORY_KEYBOARD for effect " + effectId)
+                    .that(attrs.getCategory()).isEqualTo(CATEGORY_KEYBOARD);
+        }
+    }
+
+    @Test
+    public void testVibrationAttribute_noFixAmplitude_keyboardCategoryOn_noBypassIntensityScale() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
+        mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK);
+        mockKeyboardVibrationFixedAmplitude(-1);
+        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+
+        for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
+            VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+                    effectId, /* bypassVibrationIntensitySetting= */ false);
+            assertWithMessage("Expected no FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE for effect "
+                    + effectId)
+                    .that(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)).isFalse();
+        }
+    }
+
+    @Test
+    public void testVibrationAttribute_fixAmplitude_keyboardCategoryOn_bypassIntensityScale() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
+        mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK);
+        mockKeyboardVibrationFixedAmplitude(KEYBOARD_VIBRATION_FIXED_AMPLITUDE);
+        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+
+        for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
+            VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+                    effectId, /* bypassVibrationIntensitySetting= */ false);
+            assertWithMessage("Expected FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE for effect "
+                    + effectId)
+                    .that(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)).isTrue();
+        }
+    }
+
     private HapticFeedbackVibrationProvider createProviderWithDefaultCustomizations() {
         return createProvider(/* customizations= */ null);
     }
 
     private HapticFeedbackVibrationProvider createProvider(
             SparseArray<VibrationEffect> customizations) {
-        return new HapticFeedbackVibrationProvider(
-            mResourcesMock, mVibratorInfo, customizations, mViewFeatureFlags);
+        return new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo, customizations);
     }
 
     private void mockVibratorPrimitiveSupport(int... supportedPrimitives) {
@@ -287,6 +411,11 @@
                 .thenReturn(vibrationPattern);
     }
 
+    private void mockKeyboardVibrationFixedAmplitude(float amplitude) {
+        when(mResourcesMock.getFloat(R.dimen.config_keyboardHapticFeedbackFixedAmplitude))
+                .thenReturn(amplitude);
+    }
+
     private void setupCustomizationFile(String xml) throws Exception {
         File file = new File(mContext.getCacheDir(), "test.xml");
         file.createNewFile();
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
index 1ae0966..7a2bb5a 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -69,7 +69,11 @@
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.os.test.TestLooper;
+import android.os.vibrator.Flags;
 import android.os.vibrator.VibrationConfig;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.provider.Settings;
 import android.util.ArraySet;
 import android.view.Display;
@@ -95,6 +99,9 @@
 
 public class VibrationSettingsTest {
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     private static final int UID = 1;
     private static final int VIRTUAL_DISPLAY_ID = 1;
     private static final String SYSUI_PACKAGE_NAME = "sysui";
@@ -606,6 +613,47 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED)
+    public void shouldIgnoreVibration_withKeyboardSettingsOff_shouldIgnoreKeyboardVibration() {
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_MEDIUM);
+        setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 0);
+
+        // Keyboard touch ignored.
+        assertVibrationIgnoredForAttributes(
+                new VibrationAttributes.Builder()
+                        .setUsage(USAGE_TOUCH)
+                        .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
+                        .build(),
+                Vibration.Status.IGNORED_FOR_SETTINGS);
+
+        // General touch and keyboard touch with bypass flag not ignored.
+        assertVibrationNotIgnoredForUsage(USAGE_TOUCH);
+        assertVibrationNotIgnoredForAttributes(
+                new VibrationAttributes.Builder()
+                        .setUsage(USAGE_TOUCH)
+                        .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
+                        .setFlags(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)
+                        .build());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED)
+    public void shouldIgnoreVibration_withKeyboardSettingsOn_shouldNotIgnoreKeyboardVibration() {
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 1);
+
+        // General touch ignored.
+        assertVibrationIgnoredForUsage(USAGE_TOUCH, Vibration.Status.IGNORED_FOR_SETTINGS);
+
+        // Keyboard touch not ignored.
+        assertVibrationNotIgnoredForAttributes(
+                new VibrationAttributes.Builder()
+                        .setUsage(USAGE_TOUCH)
+                        .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
+                        .build());
+    }
+
+    @Test
     public void shouldIgnoreVibrationFromVirtualDisplays_displayNonVirtual_neverIgnored() {
         // Vibrations from the primary display is never ignored regardless of the creation and
         // removal of virtual displays and of the changes of apps running on virtual displays.
@@ -895,6 +943,14 @@
                 mVibrationSettings.shouldIgnoreVibration(callerInfo));
     }
 
+    private void assertVibrationIgnoredForAttributes(VibrationAttributes attrs,
+            Vibration.Status expectedStatus) {
+        Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(attrs, UID,
+                Display.DEFAULT_DISPLAY, null, null);
+        assertEquals(errorMessageForAttributes(attrs), expectedStatus,
+                mVibrationSettings.shouldIgnoreVibration(callerInfo));
+    }
+
     private void assertVibrationNotIgnoredForUsage(@VibrationAttributes.Usage int usage) {
         assertVibrationNotIgnoredForUsageAndFlags(usage, /* flags= */ 0);
     }
@@ -919,10 +975,21 @@
                 mVibrationSettings.shouldIgnoreVibration(callerInfo));
     }
 
+    private void assertVibrationNotIgnoredForAttributes(VibrationAttributes attrs) {
+        Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(attrs, UID,
+                Display.DEFAULT_DISPLAY, null, null);
+        assertNull(errorMessageForAttributes(attrs),
+                mVibrationSettings.shouldIgnoreVibration(callerInfo));
+    }
+
     private String errorMessageForUsage(int usage) {
         return "Error for usage " + VibrationAttributes.usageToString(usage);
     }
 
+    private String errorMessageForAttributes(VibrationAttributes attrs) {
+        return "Error for attributes " + attrs;
+    }
+
     private void setDefaultIntensity(@Vibrator.VibrationIntensity int intensity) {
         when(mVibrationConfigMock.getDefaultVibrationIntensity(anyInt())).thenReturn(intensity);
     }
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 40e0e84..3dfaed6 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -82,6 +82,7 @@
 import android.os.vibrator.StepSegment;
 import android.os.vibrator.VibrationConfig;
 import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.util.ArraySet;
 import android.util.SparseArray;
@@ -89,7 +90,7 @@
 import android.view.Display;
 import android.view.HapticFeedbackConstants;
 import android.view.InputDevice;
-import android.view.flags.FeatureFlags;
+import android.view.flags.Flags;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.FlakyTest;
@@ -155,6 +156,8 @@
     @Rule
     public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
 
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Mock
     private VibratorManagerService.NativeWrapper mNativeWrapperMock;
     @Mock
@@ -175,8 +178,6 @@
     private VirtualDeviceManagerInternal mVirtualDeviceManagerInternalMock;
     @Mock
     private AudioManager mAudioManagerMock;
-    @Mock
-    private FeatureFlags mViewFeatureFlags;
 
     private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
 
@@ -326,8 +327,7 @@
                     HapticFeedbackVibrationProvider createHapticFeedbackVibrationProvider(
                             Resources resources, VibratorInfo vibratorInfo) {
                         return new HapticFeedbackVibrationProvider(
-                                resources, vibratorInfo, mHapticFeedbackVibrationMap,
-                                mViewFeatureFlags);
+                                resources, vibratorInfo, mHapticFeedbackVibrationMap);
                     }
                 });
         return mService;
@@ -1354,7 +1354,7 @@
         denyPermission(android.Manifest.permission.MODIFY_PHONE_STATE);
         denyPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING);
         // Flag override to enable the scroll feedack constants to bypass interruption policies.
-        when(mViewFeatureFlags.scrollFeedbackApi()).thenReturn(true);
+        mSetFlagsRule.enableFlags(Flags.FLAG_SCROLL_FEEDBACK_API);
         mHapticFeedbackVibrationMap.put(
                 HapticFeedbackConstants.SCROLL_TICK,
                 VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
diff --git a/services/tests/wmtests/Android.bp b/services/tests/wmtests/Android.bp
index af39b2f..1b8d746 100644
--- a/services/tests/wmtests/Android.bp
+++ b/services/tests/wmtests/Android.bp
@@ -60,6 +60,7 @@
         "truth",
         "testables",
         "hamcrest-library",
+        "flag-junit",
         "platform-compat-test-rules",
         "CtsSurfaceValidatorLib",
         "service-sdksandbox.impl",
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
index 8db09f9..61c4d06 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -46,7 +46,6 @@
 import static java.util.Collections.unmodifiableMap;
 
 import android.content.Context;
-import android.os.Looper;
 import android.os.SystemClock;
 import android.util.ArrayMap;
 import android.view.InputDevice;
@@ -99,10 +98,6 @@
      *      settings values.
      */
     protected final void setUpPhoneWindowManager(boolean supportSettingsUpdate) {
-        if (Looper.myLooper() == null) {
-            Looper.prepare();
-        }
-
         doReturn(mSettingsProviderRule.mockContentResolver(mContext))
                 .when(mContext).getContentResolver();
         mPhoneWindowManager = new TestPhoneWindowManager(mContext, supportSettingsUpdate);
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 2244dbe..261d3cc 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -78,6 +78,7 @@
 import android.os.UserHandle;
 import android.os.Vibrator;
 import android.os.VibratorInfo;
+import android.os.test.TestLooper;
 import android.service.dreams.DreamManagerInternal;
 import android.telecom.TelecomManager;
 import android.util.FeatureFlagUtils;
@@ -160,12 +161,13 @@
     @Mock private KeyguardServiceDelegate mKeyguardServiceDelegate;
 
     private StaticMockitoSession mMockitoSession;
+    private TestLooper mTestLooper = new TestLooper();
     private HandlerThread mHandlerThread;
     private Handler mHandler;
 
     private class TestInjector extends PhoneWindowManager.Injector {
         TestInjector(Context context, WindowManagerPolicy.WindowManagerFuncs funcs) {
-            super(context, funcs);
+            super(context, funcs, mTestLooper.getLooper());
         }
 
         AccessibilityShortcutController getAccessibilityShortcutController(
@@ -184,12 +186,10 @@
 
     TestPhoneWindowManager(Context context, boolean supportSettingsUpdate) {
         MockitoAnnotations.initMocks(this);
-        mHandlerThread = new HandlerThread("fake window manager");
-        mHandlerThread.start();
-        mHandler = new Handler(mHandlerThread.getLooper());
+        mHandler = new Handler(mTestLooper.getLooper());
         mContext = mockingDetails(context).isSpy() ? context : spy(context);
-        mHandler.runWithScissors(() -> setUp(supportSettingsUpdate),  0 /* timeout */);
-        waitForIdle();
+        mHandler.post(() -> setUp(supportSettingsUpdate));
+        mTestLooper.dispatchAll();
     }
 
     private void setUp(boolean supportSettingsUpdate) {
@@ -301,7 +301,6 @@
     }
 
     void tearDown() {
-        mHandlerThread.quitSafely();
         LocalServices.removeServiceForTest(InputMethodManagerInternal.class);
         Mockito.reset(mPhoneWindowManager);
         mMockitoSession.finishMocking();
@@ -327,10 +326,6 @@
         mPhoneWindowManager.dispatchUnhandledKey(null /*focusedToken*/, event, FLAG_INTERACTIVE);
     }
 
-    void waitForIdle() {
-        mHandler.runWithScissors(() -> { }, 0 /* timeout */);
-    }
-
     /**
      * Below functions will override the setting or the policy behavior.
      */
@@ -504,13 +499,13 @@
      * Below functions will check the policy behavior could be invoked.
      */
     void assertTakeScreenshotCalled() {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         verify(mDisplayPolicy, timeout(SHORTCUT_KEY_DELAY_MILLIS))
                 .takeScreenshot(anyInt(), anyInt());
     }
 
     void assertShowGlobalActionsCalled() {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         verify(mPhoneWindowManager).showGlobalActions();
         verify(mGlobalActions, timeout(SHORTCUT_KEY_DELAY_MILLIS))
                 .showDialog(anyBoolean(), anyBoolean());
@@ -519,53 +514,53 @@
     }
 
     void assertVolumeMute() {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         verify(mAudioManagerInternal, timeout(SHORTCUT_KEY_DELAY_MILLIS))
                 .silenceRingerModeInternal(eq("volume_hush"));
     }
 
     void assertAccessibilityKeychordCalled() {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         verify(mAccessibilityShortcutController,
                 timeout(SHORTCUT_KEY_DELAY_MILLIS)).performAccessibilityShortcut();
     }
 
     void assertDreamRequest() {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         verify(mDreamManagerInternal).requestDream();
     }
 
     void assertPowerSleep() {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         verify(mPowerManager,
                 timeout(SHORTCUT_KEY_DELAY_MILLIS)).goToSleep(anyLong(), anyInt(), anyInt());
     }
 
     void assertPowerWakeUp() {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         verify(mPowerManager,
                 timeout(SHORTCUT_KEY_DELAY_MILLIS)).wakeUp(anyLong(), anyInt(), anyString());
     }
 
     void assertNoPowerSleep() {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         verify(mPowerManager, never()).goToSleep(anyLong(), anyInt(), anyInt());
     }
 
     void assertCameraLaunch() {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         // GestureLauncherService should receive interceptPowerKeyDown twice.
         verify(mGestureLauncherService, times(2))
                 .interceptPowerKeyDown(any(), anyBoolean(), any());
     }
 
     void assertSearchManagerLaunchAssist() {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         verify(mSearchManager, timeout(SHORTCUT_KEY_DELAY_MILLIS)).launchAssist(any());
     }
 
     void assertLaunchCategory(String category) {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
         try {
             verify(mContext).startActivityAsUser(intentCaptor.capture(), any());
@@ -578,17 +573,17 @@
     }
 
     void assertShowRecentApps() {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         verify(mStatusBarManagerInternal).showRecentApps(anyBoolean());
     }
 
     void assertStatusBarStartAssist() {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         verify(mStatusBarManagerInternal).startAssist(any());
     }
 
     void assertSwitchKeyboardLayout(int direction) {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI)) {
             verify(mInputMethodManagerInternal).switchKeyboardLayout(eq(direction));
             verify(mWindowManagerFuncsImpl, never()).switchKeyboardLayout(anyInt(), anyInt());
@@ -599,7 +594,7 @@
     }
 
     void assertTakeBugreport() {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mContext).sendOrderedBroadcastAsUser(intentCaptor.capture(), any(), any(), any(),
                 any(), anyInt(), any(), any());
@@ -607,17 +602,17 @@
     }
 
     void assertTogglePanel() throws RemoteException {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         verify(mPhoneWindowManager.mStatusBarService).togglePanel();
     }
 
     void assertToggleShortcutsMenu() {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         verify(mStatusBarManagerInternal).toggleKeyboardShortcutsMenu(anyInt());
     }
 
     void assertToggleCapsLock() {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         verify(mInputManagerInternal).toggleCapsLock(anyInt());
     }
 
@@ -642,12 +637,12 @@
     }
 
     void assertGoToHomescreen() {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         verify(mPhoneWindowManager).launchHomeFromHotKey(anyInt());
     }
 
     void assertOpenAllAppView() {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mContext, timeout(TEST_SINGLE_KEY_DELAY_MILLIS))
                 .startActivityAsUser(intentCaptor.capture(), isNull(), any(UserHandle.class));
@@ -655,13 +650,13 @@
     }
 
     void assertNotOpenAllAppView() {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         verify(mContext, after(TEST_SINGLE_KEY_DELAY_MILLIS).never())
                 .startActivityAsUser(any(Intent.class), any(), any(UserHandle.class));
     }
 
     void assertActivityTargetLaunched(ComponentName targetActivity) {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mContext, timeout(TEST_SINGLE_KEY_DELAY_MILLIS))
                 .startActivityAsUser(intentCaptor.capture(), isNull(), any(UserHandle.class));
@@ -670,7 +665,7 @@
 
     void assertShortcutLogged(int vendorId, int productId, KeyboardLogEvent logEvent,
             int expectedKey, int expectedModifierState, String errorMsg) {
-        waitForIdle();
+        mTestLooper.dispatchAll();
         verify(() -> FrameworkStatsLog.write(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED,
                         vendorId, productId, logEvent.getIntValue(), new int[]{expectedKey},
                         expectedModifierState), description(errorMsg));
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index d2eb1cc..78566fb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -84,31 +84,49 @@
     private final ContentRecordingSession mWaitingDisplaySession =
             ContentRecordingSession.createDisplaySession(DEFAULT_DISPLAY);
     private ContentRecordingSession mTaskSession;
-    private static Point sSurfaceSize;
+    private Point mSurfaceSize;
     private ContentRecorder mContentRecorder;
     @Mock private MediaProjectionManagerWrapper mMediaProjectionManagerWrapper;
     private SurfaceControl mRecordedSurface;
 
+    private boolean mHandleAnisotropicDisplayMirroring = false;
+
     @Before public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        // GIVEN SurfaceControl can successfully mirror the provided surface.
-        sSurfaceSize = new Point(
-                mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
-                mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
-        mRecordedSurface = surfaceControlMirrors(sSurfaceSize);
-
         doReturn(INVALID_DISPLAY).when(mWm.mDisplayManagerInternal).getDisplayIdToMirror(anyInt());
 
-        // GIVEN the VirtualDisplay associated with the session (so the display has state ON).
+        // Skip unnecessary operations of relayout.
+        spyOn(mWm.mWindowPlacerLocked);
+        doNothing().when(mWm.mWindowPlacerLocked).performSurfacePlacement(anyBoolean());
+    }
+
+    private void defaultInit() {
+        createContentRecorder(createDefaultDisplayInfo());
+    }
+
+    private DisplayInfo createDefaultDisplayInfo() {
+        return createDisplayInfo(mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
+                mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
+    }
+
+    private DisplayInfo createDisplayInfo(int width, int height) {
+        // GIVEN SurfaceControl can successfully mirror the provided surface.
+        mSurfaceSize = new Point(width, height);
+        mRecordedSurface = surfaceControlMirrors(mSurfaceSize);
+
         DisplayInfo displayInfo = mDisplayInfo;
-        displayInfo.logicalWidth = sSurfaceSize.x;
-        displayInfo.logicalHeight = sSurfaceSize.y;
+        displayInfo.logicalWidth = width;
+        displayInfo.logicalHeight = height;
         displayInfo.state = STATE_ON;
+        return displayInfo;
+    }
+
+    private void createContentRecorder(DisplayInfo displayInfo) {
         mVirtualDisplayContent = createNewDisplay(displayInfo);
         final int displayId = mVirtualDisplayContent.getDisplayId();
         mContentRecorder = new ContentRecorder(mVirtualDisplayContent,
-                mMediaProjectionManagerWrapper);
+                mMediaProjectionManagerWrapper, mHandleAnisotropicDisplayMirroring);
         spyOn(mVirtualDisplayContent);
 
         // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
@@ -124,14 +142,11 @@
         // GIVEN a session is waiting for the user to review consent.
         mWaitingDisplaySession.setVirtualDisplayId(displayId);
         mWaitingDisplaySession.setWaitingForConsent(true);
-
-        // Skip unnecessary operations of relayout.
-        spyOn(mWm.mWindowPlacerLocked);
-        doNothing().when(mWm.mWindowPlacerLocked).performSurfacePlacement(anyBoolean());
     }
 
     @Test
     public void testIsCurrentlyRecording() {
+        defaultInit();
         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
 
         mContentRecorder.updateRecording();
@@ -140,6 +155,7 @@
 
     @Test
     public void testUpdateRecording_display() {
+        defaultInit();
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
@@ -147,6 +163,7 @@
 
     @Test
     public void testUpdateRecording_display_invalidDisplayIdToMirror() {
+        defaultInit();
         ContentRecordingSession session = ContentRecordingSession.createDisplaySession(
                 INVALID_DISPLAY);
         mContentRecorder.setContentRecordingSession(session);
@@ -156,6 +173,7 @@
 
     @Test
     public void testUpdateRecording_display_noDisplayContentToMirror() {
+        defaultInit();
         doReturn(null).when(
                 mWm.mRoot).getDisplayContent(anyInt());
         mContentRecorder.setContentRecordingSession(mDisplaySession);
@@ -165,6 +183,7 @@
 
     @Test
     public void testUpdateRecording_task_nullToken() {
+        defaultInit();
         ContentRecordingSession session = mTaskSession;
         session.setVirtualDisplayId(mDisplaySession.getVirtualDisplayId());
         session.setTokenToRecord(null);
@@ -176,6 +195,7 @@
 
     @Test
     public void testUpdateRecording_task_noWindowContainer() {
+        defaultInit();
         // Use the window container token of the DisplayContent, rather than task.
         ContentRecordingSession invalidTaskSession = ContentRecordingSession.createTaskSession(
                 new WindowContainer.RemoteToken(mDisplayContent));
@@ -187,6 +207,7 @@
 
     @Test
     public void testUpdateRecording_wasPaused() {
+        defaultInit();
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
 
@@ -197,6 +218,7 @@
 
     @Test
     public void testUpdateRecording_waitingForConsent() {
+        defaultInit();
         mContentRecorder.setContentRecordingSession(mWaitingDisplaySession);
         mContentRecorder.updateRecording();
         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
@@ -209,6 +231,7 @@
 
     @Test
     public void testOnConfigurationChanged_neverRecording() {
+        defaultInit();
         mContentRecorder.onConfigurationChanged(ORIENTATION_PORTRAIT);
 
         verify(mTransaction, never()).setPosition(eq(mRecordedSurface), anyFloat(), anyFloat());
@@ -218,6 +241,7 @@
 
     @Test
     public void testOnConfigurationChanged_resizesSurface() {
+        defaultInit();
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
         // Ensure a different orientation when we check if something has changed.
@@ -234,13 +258,14 @@
 
     @Test
     public void testOnConfigurationChanged_resizesVirtualDisplay() {
+        defaultInit();
         final int newWidth = 55;
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
 
         // The user rotates the device, so the host app resizes the virtual display for the capture.
-        resizeDisplay(mDisplayContent, newWidth, sSurfaceSize.y);
-        resizeDisplay(mVirtualDisplayContent, newWidth, sSurfaceSize.y);
+        resizeDisplay(mDisplayContent, newWidth, mSurfaceSize.y);
+        resizeDisplay(mVirtualDisplayContent, newWidth, mSurfaceSize.y);
         mContentRecorder.onConfigurationChanged(mDisplayContent.getConfiguration().orientation);
 
         verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(),
@@ -251,6 +276,7 @@
 
     @Test
     public void testOnConfigurationChanged_rotateVirtualDisplay() {
+        defaultInit();
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
 
@@ -271,12 +297,13 @@
      */
     @Test
     public void testOnConfigurationChanged_resizeSurface() {
+        defaultInit();
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
 
         // Resize the output surface.
-        final Point newSurfaceSize = new Point(Math.round(sSurfaceSize.x / 2f),
-                Math.round(sSurfaceSize.y * 2));
+        final Point newSurfaceSize = new Point(Math.round(mSurfaceSize.x / 2f),
+                Math.round(mSurfaceSize.y * 2));
         doReturn(newSurfaceSize).when(mWm.mDisplayManagerInternal).getDisplaySurfaceDefaultSize(
                 anyInt());
         mContentRecorder.onConfigurationChanged(
@@ -292,6 +319,7 @@
 
     @Test
     public void testOnTaskOrientationConfigurationChanged_resizesSurface() {
+        defaultInit();
         mContentRecorder.setContentRecordingSession(mTaskSession);
         mContentRecorder.updateRecording();
 
@@ -314,6 +342,7 @@
 
     @Test
     public void testOnTaskBoundsConfigurationChanged_notifiesCallback() {
+        defaultInit();
         mTask.getRootTask().setWindowingMode(WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
 
         final int minWidth = 222;
@@ -351,6 +380,7 @@
 
     @Test
     public void testTaskWindowingModeChanged_pip_stopsRecording() {
+        defaultInit();
         // WHEN a recording is ongoing.
         mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         mContentRecorder.setContentRecordingSession(mTaskSession);
@@ -368,6 +398,7 @@
 
     @Test
     public void testTaskWindowingModeChanged_fullscreen_startsRecording() {
+        defaultInit();
         // WHEN a recording is ongoing.
         mTask.setWindowingMode(WINDOWING_MODE_PINNED);
         mContentRecorder.setContentRecordingSession(mTaskSession);
@@ -384,6 +415,7 @@
 
     @Test
     public void testStartRecording_notifiesCallback_taskSession() {
+        defaultInit();
         // WHEN a recording is ongoing.
         mContentRecorder.setContentRecordingSession(mTaskSession);
         mContentRecorder.updateRecording();
@@ -396,6 +428,7 @@
 
     @Test
     public void testStartRecording_notifiesCallback_displaySession() {
+        defaultInit();
         // WHEN a recording is ongoing.
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
@@ -408,6 +441,7 @@
 
     @Test
     public void testStartRecording_taskInPIP_recordingNotStarted() {
+        defaultInit();
         // GIVEN a task is in PIP.
         mContentRecorder.setContentRecordingSession(mTaskSession);
         mTask.setWindowingMode(WINDOWING_MODE_PINNED);
@@ -421,6 +455,7 @@
 
     @Test
     public void testStartRecording_taskInSplit_recordingStarted() {
+        defaultInit();
         // GIVEN a task is in PIP.
         mContentRecorder.setContentRecordingSession(mTaskSession);
         mTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
@@ -434,6 +469,7 @@
 
     @Test
     public void testStartRecording_taskInFullscreen_recordingStarted() {
+        defaultInit();
         // GIVEN a task is in PIP.
         mContentRecorder.setContentRecordingSession(mTaskSession);
         mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
@@ -447,6 +483,7 @@
 
     @Test
     public void testOnVisibleRequestedChanged_notifiesCallback() {
+        defaultInit();
         // WHEN a recording is ongoing.
         mContentRecorder.setContentRecordingSession(mTaskSession);
         mContentRecorder.updateRecording();
@@ -471,6 +508,7 @@
 
     @Test
     public void testOnVisibleRequestedChanged_noRecording_doesNotNotifyCallback() {
+        defaultInit();
         // WHEN a recording is not ongoing.
         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
 
@@ -493,6 +531,7 @@
 
     @Test
     public void testPauseRecording_pausesRecording() {
+        defaultInit();
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
 
@@ -502,12 +541,14 @@
 
     @Test
     public void testPauseRecording_neverRecording() {
+        defaultInit();
         mContentRecorder.pauseRecording();
         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
     }
 
     @Test
     public void testStopRecording_stopsRecording() {
+        defaultInit();
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
 
@@ -517,12 +558,14 @@
 
     @Test
     public void testStopRecording_neverRecording() {
+        defaultInit();
         mContentRecorder.stopRecording();
         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
     }
 
     @Test
     public void testRemoveTask_stopsRecording() {
+        defaultInit();
         mContentRecorder.setContentRecordingSession(mTaskSession);
         mContentRecorder.updateRecording();
 
@@ -533,6 +576,7 @@
 
     @Test
     public void testRemoveTask_stopsRecording_nullSessionShouldNotThrowExceptions() {
+        defaultInit();
         mContentRecorder.setContentRecordingSession(mTaskSession);
         mContentRecorder.updateRecording();
         mContentRecorder.setContentRecordingSession(null);
@@ -541,6 +585,7 @@
 
     @Test
     public void testUpdateMirroredSurface_capturedAreaResized() {
+        defaultInit();
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
@@ -548,9 +593,9 @@
         // WHEN attempting to mirror on the virtual display, and the captured content is resized.
         float xScale = 0.7f;
         float yScale = 2f;
-        Rect displayAreaBounds = new Rect(0, 0, Math.round(sSurfaceSize.x * xScale),
-                Math.round(sSurfaceSize.y * yScale));
-        mContentRecorder.updateMirroredSurface(mTransaction, displayAreaBounds, sSurfaceSize);
+        Rect displayAreaBounds = new Rect(0, 0, Math.round(mSurfaceSize.x * xScale),
+                Math.round(mSurfaceSize.y * yScale));
+        mContentRecorder.updateMirroredSurface(mTransaction, displayAreaBounds, mSurfaceSize);
         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
 
         // THEN content in the captured DisplayArea is scaled to fit the surface size.
@@ -558,7 +603,7 @@
                 1.0f / yScale);
         // THEN captured content is positioned in the centre of the output surface.
         int scaledWidth = Math.round((float) displayAreaBounds.width() / xScale);
-        int xInset = (sSurfaceSize.x - scaledWidth) / 2;
+        int xInset = (mSurfaceSize.x - scaledWidth) / 2;
         verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface, xInset, 0);
         // THEN the resize callback is notified.
         verify(mMediaProjectionManagerWrapper).notifyActiveProjectionCapturedContentResized(
@@ -566,7 +611,131 @@
     }
 
     @Test
+    public void testUpdateMirroredSurface_isotropicPixel() {
+        mHandleAnisotropicDisplayMirroring = false;
+        DisplayInfo displayInfo = createDefaultDisplayInfo();
+        createContentRecorder(displayInfo);
+        mContentRecorder.setContentRecordingSession(mDisplaySession);
+        mContentRecorder.updateRecording();
+        assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+        verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, 1, 0, 0, 1);
+    }
+
+    @Test
+    public void testUpdateMirroredSurface_anisotropicPixel_compressY() {
+        mHandleAnisotropicDisplayMirroring = true;
+        DisplayInfo displayInfo = createDefaultDisplayInfo();
+        DisplayInfo inputDisplayInfo =
+                mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY).getDisplayInfo();
+        displayInfo.physicalXDpi = 2.0f * inputDisplayInfo.physicalXDpi;
+        displayInfo.physicalYDpi = inputDisplayInfo.physicalYDpi;
+        createContentRecorder(displayInfo);
+        mContentRecorder.setContentRecordingSession(mDisplaySession);
+        mContentRecorder.updateRecording();
+        assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+        float xScale = 1f;
+        float yScale = 0.5f;
+        verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, xScale, 0, 0,
+                yScale);
+        verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface, 0,
+                Math.round(0.25 * mSurfaceSize.y));
+    }
+
+    @Test
+    public void testUpdateMirroredSurface_anisotropicPixel_compressX() {
+        mHandleAnisotropicDisplayMirroring = true;
+        DisplayInfo displayInfo = createDefaultDisplayInfo();
+        DisplayInfo inputDisplayInfo =
+                mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY).getDisplayInfo();
+        displayInfo.physicalXDpi = inputDisplayInfo.physicalXDpi;
+        displayInfo.physicalYDpi = 2.0f * inputDisplayInfo.physicalYDpi;
+        createContentRecorder(displayInfo);
+        mContentRecorder.setContentRecordingSession(mDisplaySession);
+        mContentRecorder.updateRecording();
+        assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+        float xScale = 0.5f;
+        float yScale = 1f;
+        verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, xScale, 0, 0,
+                yScale);
+        verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface,
+                Math.round(0.25 * mSurfaceSize.x), 0);
+    }
+
+    @Test
+    public void testUpdateMirroredSurface_anisotropicPixel_scaleOnX() {
+        mHandleAnisotropicDisplayMirroring = true;
+        int width = 2 * mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width();
+        int height = 6 * mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height();
+        DisplayInfo displayInfo = createDisplayInfo(width, height);
+        DisplayInfo inputDisplayInfo =
+                mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY).getDisplayInfo();
+        displayInfo.physicalXDpi = inputDisplayInfo.physicalXDpi;
+        displayInfo.physicalYDpi = 2.0f * inputDisplayInfo.physicalYDpi;
+        createContentRecorder(displayInfo);
+        mContentRecorder.setContentRecordingSession(mDisplaySession);
+        mContentRecorder.updateRecording();
+        assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+        float xScale = 2f;
+        float yScale = 4f;
+        verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, xScale, 0, 0,
+                yScale);
+        verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface, 0,
+                inputDisplayInfo.logicalHeight);
+    }
+
+    @Test
+    public void testUpdateMirroredSurface_anisotropicPixel_scaleOnY() {
+        mHandleAnisotropicDisplayMirroring = true;
+        int width = 6 * mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width();
+        int height = 2 * mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height();
+        DisplayInfo displayInfo = createDisplayInfo(width, height);
+        DisplayInfo inputDisplayInfo =
+                mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY).getDisplayInfo();
+        displayInfo.physicalXDpi = 2.0f * inputDisplayInfo.physicalXDpi;
+        displayInfo.physicalYDpi = inputDisplayInfo.physicalYDpi;
+        createContentRecorder(displayInfo);
+        mContentRecorder.setContentRecordingSession(mDisplaySession);
+        mContentRecorder.updateRecording();
+        assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+        float xScale = 4f;
+        float yScale = 2f;
+        verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, xScale, 0, 0,
+                yScale);
+        verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface,
+                inputDisplayInfo.logicalWidth, 0);
+    }
+
+    @Test
+    public void testUpdateMirroredSurface_anisotropicPixel_shrinkCanvas() {
+        mHandleAnisotropicDisplayMirroring = true;
+        int width = mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width() / 2;
+        int height = mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height() / 2;
+        DisplayInfo displayInfo = createDisplayInfo(width, height);
+        DisplayInfo inputDisplayInfo =
+                mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY).getDisplayInfo();
+        displayInfo.physicalXDpi = 2f * inputDisplayInfo.physicalXDpi;
+        displayInfo.physicalYDpi = inputDisplayInfo.physicalYDpi;
+        createContentRecorder(displayInfo);
+        mContentRecorder.setContentRecordingSession(mDisplaySession);
+        mContentRecorder.updateRecording();
+        assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+        float xScale = 0.5f;
+        float yScale = 0.25f;
+        verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, xScale, 0, 0,
+                yScale);
+        verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface, 0,
+                (mSurfaceSize.y - height / 2) / 2);
+    }
+
+    @Test
     public void testDisplayContentUpdatesRecording_withoutSurface() {
+        defaultInit();
         // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
         // mirror.
         setUpDefaultTaskDisplayAreaWindowToken();
@@ -585,6 +754,7 @@
 
     @Test
     public void testDisplayContentUpdatesRecording_withSurface() {
+        defaultInit();
         // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
         // mirror.
         setUpDefaultTaskDisplayAreaWindowToken();
@@ -602,12 +772,13 @@
 
     @Test
     public void testDisplayContentUpdatesRecording_displayMirroring() {
+        defaultInit();
         // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
         // mirror.
         setUpDefaultTaskDisplayAreaWindowToken();
 
         // GIVEN SurfaceControl can successfully mirror the provided surface.
-        surfaceControlMirrors(sSurfaceSize);
+        surfaceControlMirrors(mSurfaceSize);
         // Initially disable getDisplayIdToMirror since the DMS may create the DC outside the direct
         // call in the test. We need to spy on the DC before updateRecording is called or we can't
         // verify setDisplayMirroring is called
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index 84d42d42..6a738be 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -163,8 +163,8 @@
     public void testUpdateDimsAppliesCrop() {
         mHost.addChild(mChild, 0);
 
-        final float alpha = 0.8f;
-        mDimmer.dimAbove(mChild, alpha);
+        mDimmer.adjustAppearance(mChild, 1, 1);
+        mDimmer.adjustRelativeLayer(mChild, -1);
 
         int width = 100;
         int height = 300;
@@ -176,42 +176,13 @@
     }
 
     @Test
-    public void testDimAboveWithChildCreatesSurfaceAboveChild_Smooth() {
-        assumeTrue(Flags.dimmerRefactor());
-        final float alpha = 0.8f;
-        mHost.addChild(mChild, 0);
-        mDimmer.dimAbove(mChild, alpha);
-        SurfaceControl dimLayer = mDimmer.getDimLayer();
-
-        assertNotNull("Dimmer should have created a surface", dimLayer);
-
-        mDimmer.updateDims(mTransaction);
-        verify(sTestAnimation).startAnimation(eq(dimLayer), eq(mTransaction),
-                anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class));
-        verify(mTransaction).setRelativeLayer(dimLayer, mChild.mControl, 1);
-        verify(mTransaction, lastCall()).setAlpha(dimLayer, alpha);
-    }
-
-    @Test
-    public void testDimAboveWithChildCreatesSurfaceAboveChild_Legacy() {
-        assumeFalse(Flags.dimmerRefactor());
-        final float alpha = 0.8f;
-        mHost.addChild(mChild, 0);
-        mDimmer.dimAbove(mChild, alpha);
-        SurfaceControl dimLayer = mDimmer.getDimLayer();
-
-        assertNotNull("Dimmer should have created a surface", dimLayer);
-
-        verify(mHost.getPendingTransaction()).setAlpha(dimLayer, alpha);
-        verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, mChild.mControl, 1);
-    }
-
-    @Test
     public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Smooth() {
         assumeTrue(Flags.dimmerRefactor());
         final float alpha = 0.7f;
+        final int blur = 50;
         mHost.addChild(mChild, 0);
-        mDimmer.dimBelow(mChild, alpha, 50);
+        mDimmer.adjustAppearance(mChild, alpha, blur);
+        mDimmer.adjustRelativeLayer(mChild, -1);
         SurfaceControl dimLayer = mDimmer.getDimLayer();
 
         assertNotNull("Dimmer should have created a surface", dimLayer);
@@ -221,7 +192,7 @@
                 anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class));
         verify(mTransaction).setRelativeLayer(dimLayer, mChild.mControl, -1);
         verify(mTransaction, lastCall()).setAlpha(dimLayer, alpha);
-        verify(mTransaction).setBackgroundBlurRadius(dimLayer, 50);
+        verify(mTransaction).setBackgroundBlurRadius(dimLayer, blur);
     }
 
     @Test
@@ -229,7 +200,8 @@
         assumeFalse(Flags.dimmerRefactor());
         final float alpha = 0.7f;
         mHost.addChild(mChild, 0);
-        mDimmer.dimBelow(mChild, alpha, 50);
+        mDimmer.adjustAppearance(mChild, alpha, 20);
+        mDimmer.adjustRelativeLayer(mChild, -1);
         SurfaceControl dimLayer = mDimmer.getDimLayer();
 
         assertNotNull("Dimmer should have created a surface", dimLayer);
@@ -244,12 +216,15 @@
         mHost.addChild(mChild, 0);
 
         final float alpha = 0.8f;
+        final int blur = 50;
         // Dim once
-        mDimmer.dimBelow(mChild, alpha, 0);
+        mDimmer.adjustAppearance(mChild, alpha, blur);
+        mDimmer.adjustRelativeLayer(mChild, -1);
         SurfaceControl dimLayer = mDimmer.getDimLayer();
         mDimmer.updateDims(mTransaction);
         // Reset, and don't dim
         mDimmer.resetDimStates();
+        mDimmer.adjustRelativeLayer(mChild, -1);
         mDimmer.updateDims(mTransaction);
         verify(mTransaction).show(dimLayer);
         verify(mTransaction).remove(dimLayer);
@@ -261,7 +236,8 @@
         mHost.addChild(mChild, 0);
 
         final float alpha = 0.8f;
-        mDimmer.dimAbove(mChild, alpha);
+        mDimmer.adjustAppearance(mChild, alpha, 20);
+        mDimmer.adjustRelativeLayer(mChild, -1);
         SurfaceControl dimLayer = mDimmer.getDimLayer();
         mDimmer.resetDimStates();
 
@@ -278,13 +254,16 @@
         mHost.addChild(mChild, 0);
 
         final float alpha = 0.8f;
+        final int blur = 20;
         // Dim once
-        mDimmer.dimBelow(mChild, alpha, 0);
+        mDimmer.adjustAppearance(mChild, alpha, blur);
+        mDimmer.adjustRelativeLayer(mChild, -1);
         SurfaceControl dimLayer = mDimmer.getDimLayer();
         mDimmer.updateDims(mTransaction);
         // Reset and dim again
         mDimmer.resetDimStates();
-        mDimmer.dimAbove(mChild, alpha);
+        mDimmer.adjustAppearance(mChild, alpha, blur);
+        mDimmer.adjustRelativeLayer(mChild, -1);
         mDimmer.updateDims(mTransaction);
         verify(mTransaction).show(dimLayer);
         verify(mTransaction, never()).remove(dimLayer);
@@ -294,7 +273,8 @@
     public void testDimUpdateWhileDimming() {
         mHost.addChild(mChild, 0);
         final float alpha = 0.8f;
-        mDimmer.dimAbove(mChild, alpha);
+        mDimmer.adjustAppearance(mChild, alpha, 20);
+        mDimmer.adjustRelativeLayer(mChild, -1);
         final Rect bounds = mDimmer.getDimBounds();
 
         SurfaceControl dimLayer = mDimmer.getDimLayer();
@@ -314,7 +294,8 @@
     public void testRemoveDimImmediately_Smooth() {
         assumeTrue(Flags.dimmerRefactor());
         mHost.addChild(mChild, 0);
-        mDimmer.dimAbove(mChild, 1);
+        mDimmer.adjustAppearance(mChild, 1, 2);
+        mDimmer.adjustRelativeLayer(mChild, -1);
         SurfaceControl dimLayer = mDimmer.getDimLayer();
         mDimmer.updateDims(mTransaction);
         verify(mTransaction, times(1)).show(dimLayer);
@@ -333,7 +314,8 @@
     public void testRemoveDimImmediately_Legacy() {
         assumeFalse(Flags.dimmerRefactor());
         mHost.addChild(mChild, 0);
-        mDimmer.dimAbove(mChild, 1);
+        mDimmer.adjustAppearance(mChild, 1, 0);
+        mDimmer.adjustRelativeLayer(mChild, -1);
         SurfaceControl dimLayer = mDimmer.getDimLayer();
         mDimmer.updateDims(mTransaction);
         verify(mTransaction, times(1)).show(dimLayer);
@@ -351,16 +333,16 @@
     @Test
     public void testDimmerWithBlurUpdatesTransaction_Legacy() {
         assumeFalse(Flags.dimmerRefactor());
-        TestWindowContainer child = new TestWindowContainer(mWm);
-        mHost.addChild(child, 0);
+        mHost.addChild(mChild, 0);
 
         final int blurRadius = 50;
-        mDimmer.dimBelow(child, 0, blurRadius);
+        mDimmer.adjustAppearance(mChild, 1, blurRadius);
+        mDimmer.adjustRelativeLayer(mChild, -1);
         SurfaceControl dimLayer = mDimmer.getDimLayer();
 
         assertNotNull("Dimmer should have created a surface", dimLayer);
 
         verify(mHost.getPendingTransaction()).setBackgroundBlurRadius(dimLayer, blurRadius);
-        verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, child.mControl, -1);
+        verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, mChild.mControl, -1);
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java
index 4864868..3cb4a1d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java
@@ -19,6 +19,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 
 import android.os.Handler;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.DexmakerShareClassLoaderRule;
 
 import org.junit.Rule;
@@ -27,11 +28,16 @@
 
 /** The base class which provides the common rule for test classes under wm package. */
 class SystemServiceTestsBase {
-    @Rule
+    @Rule(order = 0)
     public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
             new DexmakerShareClassLoaderRule();
-    @Rule
-    public final SystemServicesTestRule mSystemServicesTestRule = new SystemServicesTestRule();
+
+    @Rule(order = 1)
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+    @Rule(order = 2)
+    public final SystemServicesTestRule mSystemServicesTestRule = new SystemServicesTestRule(
+            this::onBeforeSystemServicesCreated);
 
     @WindowTestRunner.MethodWrapperRule
     public final WindowManagerGlobalLockRule mLockRule =
@@ -65,6 +71,11 @@
     }
 
     /**
+     * Called before system services are created
+     */
+    protected void onBeforeSystemServicesCreated() {}
+
+    /**
      * Make the system booted, so that {@link ActivityStack#resumeTopActivityInnerLocked} can really
      * be executed to update activity state and configuration when resuming the current top.
      */
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 7634d9f..2597465 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -134,11 +134,20 @@
     private WindowState.PowerManagerWrapper mPowerManagerWrapper;
     private InputManagerService mImService;
     private InputChannel mInputChannel;
+    private Runnable mOnBeforeServicesCreated;
     /**
      * Spied {@link SurfaceControl.Transaction} class than can be used to verify calls.
      */
     SurfaceControl.Transaction mTransaction;
 
+    public SystemServicesTestRule(Runnable onBeforeServicesCreated) {
+        mOnBeforeServicesCreated = onBeforeServicesCreated;
+    }
+
+    public SystemServicesTestRule() {
+        this(/* onBeforeServicesCreated= */ null);
+    }
+
     @Override
     public Statement apply(Statement base, Description description) {
         return new Statement() {
@@ -168,6 +177,10 @@
     }
 
     private void setUp() {
+        if (mOnBeforeServicesCreated != null) {
+            mOnBeforeServicesCreated.run();
+        }
+
         // Use stubOnly() to reduce memory usage if it doesn't need verification.
         final MockSettings spyStubOnly = withSettings().stubOnly()
                 .defaultAnswer(CALLS_REAL_METHODS);
diff --git a/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
index 75a8dd8..085eddd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import android.platform.test.annotations.Presubmit;
@@ -94,10 +95,14 @@
     public void testRemoveFinishingInvisibleActivityFromUnknown() {
         final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
         mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(activity);
-        activity.finishing = true;
-        activity.setVisibleRequested(true);
-        activity.setVisibility(false);
+        assertFalse(mDisplayContent.mUnknownAppVisibilityController.allResolved());
+        activity.makeFinishingLocked();
         assertTrue(mDisplayContent.mUnknownAppVisibilityController.allResolved());
+
+        mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(activity);
+        assertTrue(mDisplayContent.mUnknownAppVisibilityController.isVisibilityUnknown(activity));
+        activity.setState(ActivityRecord.State.STOPPED, "test");
+        assertFalse(mDisplayContent.mUnknownAppVisibilityController.isVisibilityUnknown(activity));
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index e86fc36..eaeb804 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -100,6 +100,9 @@
 import com.android.compatibility.common.util.AdoptShellPermissionsRule;
 import com.android.internal.os.IResultReceiver;
 import com.android.server.LocalServices;
+import com.android.server.wm.WindowManagerService.WindowContainerInfo;
+
+import com.google.common.truth.Expect;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -125,6 +128,9 @@
             InstrumentationRegistry.getInstrumentation().getUiAutomation(),
             ADD_TRUSTED_DISPLAY);
 
+    @Rule
+    public Expect mExpect = Expect.create();
+
     @Test
     public void testIsRequestedOrientationMapped() {
         mWm.setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled*/ true,
@@ -674,64 +680,68 @@
 
     @Test
     public void testGetTaskWindowContainerTokenForLaunchCookie_nullCookie() {
-        WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(null);
-        assertThat(wct).isNull();
+        WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(null);
+        assertThat(wci).isNull();
     }
 
     @Test
     public void testGetTaskWindowContainerTokenForLaunchCookie_invalidCookie() {
         Binder cookie = new Binder("test cookie");
-        WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie);
-        assertThat(wct).isNull();
+        WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie);
+        assertThat(wci).isNull();
 
         final ActivityRecord testActivity = new ActivityBuilder(mAtm)
                 .setCreateTask(true)
                 .build();
 
-        wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie);
-        assertThat(wct).isNull();
+        wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie);
+        assertThat(wci).isNull();
     }
 
     @Test
     public void testGetTaskWindowContainerTokenForLaunchCookie_validCookie() {
         final Binder cookie = new Binder("ginger cookie");
         final WindowContainerToken launchRootTask = mock(WindowContainerToken.class);
-        setupActivityWithLaunchCookie(cookie, launchRootTask);
+        final int uid = 123;
+        setupActivityWithLaunchCookie(cookie, launchRootTask, uid);
 
-        WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie);
-        assertThat(wct).isEqualTo(launchRootTask);
+        WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie);
+        mExpect.that(wci.getToken()).isEqualTo(launchRootTask);
+        mExpect.that(wci.getUid()).isEqualTo(uid);
     }
 
     @Test
     public void testGetTaskWindowContainerTokenForLaunchCookie_multipleCookies() {
         final Binder cookie1 = new Binder("ginger cookie");
         final WindowContainerToken launchRootTask1 = mock(WindowContainerToken.class);
-        setupActivityWithLaunchCookie(cookie1, launchRootTask1);
+        final int uid1 = 123;
+        setupActivityWithLaunchCookie(cookie1, launchRootTask1, uid1);
 
         setupActivityWithLaunchCookie(new Binder("choc chip cookie"),
-                mock(WindowContainerToken.class));
+                mock(WindowContainerToken.class), /* uid= */ 456);
 
         setupActivityWithLaunchCookie(new Binder("peanut butter cookie"),
-                mock(WindowContainerToken.class));
+                mock(WindowContainerToken.class), /* uid= */ 789);
 
-        WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie1);
-        assertThat(wct).isEqualTo(launchRootTask1);
+        WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie1);
+        mExpect.that(wci.getToken()).isEqualTo(launchRootTask1);
+        mExpect.that(wci.getUid()).isEqualTo(uid1);
     }
 
     @Test
     public void testGetTaskWindowContainerTokenForLaunchCookie_multipleCookies_noneValid() {
         setupActivityWithLaunchCookie(new Binder("ginger cookie"),
-                mock(WindowContainerToken.class));
+                mock(WindowContainerToken.class), /* uid= */ 123);
 
         setupActivityWithLaunchCookie(new Binder("choc chip cookie"),
-                mock(WindowContainerToken.class));
+                mock(WindowContainerToken.class), /* uid= */ 456);
 
         setupActivityWithLaunchCookie(new Binder("peanut butter cookie"),
-                mock(WindowContainerToken.class));
+                mock(WindowContainerToken.class), /* uid= */ 789);
 
-        WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(
+        WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(
                 new Binder("some other cookie"));
-        assertThat(wct).isNull();
+        assertThat(wci).isNull();
     }
 
     @Test
@@ -778,17 +788,18 @@
     }
 
     @Test
-    public void setContentRecordingSession_matchingTask_mutatesSessionWithWindowContainerToken() {
+    public void setContentRecordingSession_matchingTask_mutatesSessionWithWindowContainerInfo() {
         WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
         Task task = createTask(mDefaultDisplay);
         ActivityRecord activityRecord = createActivityRecord(task);
-        ContentRecordingSession session = ContentRecordingSession.createTaskSession(
-                activityRecord.mLaunchCookie);
+        ContentRecordingSession session =
+                ContentRecordingSession.createTaskSession(activityRecord.mLaunchCookie);
 
         wmInternal.setContentRecordingSession(session);
 
-        assertThat(session.getTokenToRecord()).isEqualTo(
-                task.mRemoteToken.toWindowContainerToken().asBinder());
+        mExpect.that(session.getTokenToRecord())
+                .isEqualTo(task.mRemoteToken.toWindowContainerToken().asBinder());
+        mExpect.that(session.getTargetUid()).isEqualTo(activityRecord.getUid());
     }
 
     @Test
@@ -1010,12 +1021,12 @@
         }
     }
 
-    private void setupActivityWithLaunchCookie(IBinder launchCookie, WindowContainerToken wct) {
+    private void setupActivityWithLaunchCookie(
+            IBinder launchCookie, WindowContainerToken wct, int uid) {
         final WindowContainer.RemoteToken remoteToken = mock(WindowContainer.RemoteToken.class);
         when(remoteToken.toWindowContainerToken()).thenReturn(wct);
-        final ActivityRecord testActivity = new ActivityBuilder(mAtm)
-                .setCreateTask(true)
-                .build();
+        final ActivityRecord testActivity =
+                new ActivityBuilder(mAtm).setCreateTask(true).setUid(uid).build();
         testActivity.mLaunchCookie = launchCookie;
         testActivity.getTask().mRemoteToken = remoteToken;
     }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
index 42b08e3..93b5a40 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
@@ -20,6 +20,8 @@
 import static android.Manifest.permission.LOG_COMPAT_CHANGE;
 import static android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG;
 import static android.Manifest.permission.RECORD_AUDIO;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_DEFAULT;
 import static android.service.attention.AttentionService.PROXIMITY_UNKNOWN;
 import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_EXTERNAL;
 import static android.service.voice.HotwordDetectionService.ENABLE_PROXIMITY_RESULT;
@@ -753,11 +755,21 @@
                                 "Failed to obtain permission RECORD_AUDIO for identity "
                                         + mVoiceInteractorIdentity);
                     }
-                    mAppOpsManager.noteOpNoThrow(
-                            AppOpsPolicy.getVoiceActivationOp(),
-                            mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
-                            mVoiceInteractorIdentity.attributionTag,
-                                    HOTWORD_DETECTION_OP_MESSAGE);
+                    int opMode = mAppOpsManager.unsafeCheckOpNoThrow(
+                            AppOpsManager.opToPublicName(AppOpsPolicy.getVoiceActivationOp()),
+                            mVoiceInteractorIdentity.uid,
+                            mVoiceInteractorIdentity.packageName);
+                    if (opMode == MODE_DEFAULT || opMode == MODE_ALLOWED) {
+                        mAppOpsManager.noteOpNoThrow(
+                                AppOpsPolicy.getVoiceActivationOp(),
+                                mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
+                                mVoiceInteractorIdentity.attributionTag,
+                                HOTWORD_DETECTION_OP_MESSAGE);
+                    } else {
+                        throw new SecurityException(
+                                "The app op OP_RECEIVE_SANDBOX_TRIGGER_AUDIO is denied for "
+                                        + "identity" + mVoiceInteractorIdentity);
+                    }
                 } else {
                     enforcePermissionForDataDelivery(mContext, mVoiceInteractorIdentity,
                             RECORD_AUDIO, HOTWORD_DETECTION_OP_MESSAGE);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 7a0bf90..540cecf 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1056,6 +1056,14 @@
             "carrier_use_ims_first_for_emergency_bool";
 
     /**
+     * When {@code true}, this carrier will preferentially dial normal routed emergency calls over
+     * an in-service SIM if one is available.
+     * @hide
+     */
+    public static final String KEY_PREFER_IN_SERVICE_SIM_FOR_NORMAL_ROUTED_EMERGENCY_CALLS_BOOL =
+            "prefer_in_service_sim_for_normal_routed_emergency_calls_bool";
+
+    /**
      * When {@code true}, the determination of whether to place a call as an emergency call will be
      * based on the known {@link android.telephony.emergency.EmergencyNumber}s for the SIM on which
      * the call is being placed. In a dual SIM scenario, if Sim A has the emergency numbers
@@ -9874,6 +9882,8 @@
         sDefaults.putBoolean(KEY_CARRIER_IMS_GBA_REQUIRED_BOOL, false);
         sDefaults.putBoolean(KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL, false);
         sDefaults.putBoolean(KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL, true);
+        sDefaults.putBoolean(KEY_PREFER_IN_SERVICE_SIM_FOR_NORMAL_ROUTED_EMERGENCY_CALLS_BOOL,
+                false);
         sDefaults.putBoolean(KEY_USE_ONLY_DIALED_SIM_ECC_LIST_BOOL, false);
         sDefaults.putString(KEY_CARRIER_NETWORK_SERVICE_WWAN_PACKAGE_OVERRIDE_STRING, "");
         sDefaults.putString(KEY_CARRIER_NETWORK_SERVICE_WLAN_PACKAGE_OVERRIDE_STRING, "");
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index e9af486..11cbcb1 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -15,6 +15,7 @@
  */
 package android.telephony.data;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -35,6 +36,7 @@
 import android.util.ArrayMap;
 import android.util.Log;
 
+import com.android.internal.telephony.flags.Flags;
 import com.android.telephony.Rlog;
 
 import java.lang.annotation.Retention;
@@ -121,6 +123,9 @@
     public static final int TYPE_BIP = ApnTypes.BIP;
     /** APN type for ENTERPRISE. */
     public static final int TYPE_ENTERPRISE = ApnTypes.ENTERPRISE;
+    /** APN type for RCS (Rich Communication Services). */
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    public static final int TYPE_RCS = ApnTypes.RCS;
 
     /** @hide */
     @IntDef(flag = true, prefix = {"TYPE_"}, value = {
@@ -139,6 +144,7 @@
             TYPE_BIP,
             TYPE_VSIM,
             TYPE_ENTERPRISE,
+            TYPE_RCS
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ApnType {
@@ -356,6 +362,17 @@
     @SystemApi
     public static final String TYPE_ENTERPRISE_STRING = "enterprise";
 
+    /**
+     * APN type for RCS (Rich Communication Services)
+     *
+     * Note: String representations of APN types are intended for system apps to communicate with
+     * modem components or carriers. Non-system apps should use the integer variants instead.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    @SystemApi
+    public static final String TYPE_RCS_STRING = "rcs";
+
 
     /** @hide */
     @IntDef(prefix = { "AUTH_TYPE_" }, value = {
@@ -424,6 +441,26 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface MvnoType {}
 
+    /**
+     * Indicating this APN can be used when the device is using terrestrial cellular networks.
+     * @hide
+     */
+    public static final int INFRASTRUCTURE_CELLULAR = 1 << 0;
+
+    /**
+     * Indicating this APN can be used when the device is attached to satellites.
+     * @hide
+     */
+    public static final int INFRASTRUCTURE_SATELLITE = 1 << 1;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "INFRASTRUCTURE_" }, value = {
+            INFRASTRUCTURE_CELLULAR,
+            INFRASTRUCTURE_SATELLITE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface InfrastructureBitmask {}
+
     private static final Map<String, Integer> APN_TYPE_STRING_MAP;
     private static final Map<Integer, String> APN_TYPE_INT_MAP;
     private static final Map<String, Integer> PROTOCOL_STRING_MAP;
@@ -449,6 +486,7 @@
         APN_TYPE_STRING_MAP.put(TYPE_ENTERPRISE_STRING, TYPE_ENTERPRISE);
         APN_TYPE_STRING_MAP.put(TYPE_VSIM_STRING, TYPE_VSIM);
         APN_TYPE_STRING_MAP.put(TYPE_BIP_STRING, TYPE_BIP);
+        APN_TYPE_STRING_MAP.put(TYPE_RCS_STRING, TYPE_RCS);
 
         APN_TYPE_INT_MAP = new ArrayMap<>();
         APN_TYPE_INT_MAP.put(TYPE_DEFAULT, TYPE_DEFAULT_STRING);
@@ -466,6 +504,7 @@
         APN_TYPE_INT_MAP.put(TYPE_ENTERPRISE, TYPE_ENTERPRISE_STRING);
         APN_TYPE_INT_MAP.put(TYPE_VSIM, TYPE_VSIM_STRING);
         APN_TYPE_INT_MAP.put(TYPE_BIP, TYPE_BIP_STRING);
+        APN_TYPE_INT_MAP.put(TYPE_RCS, TYPE_RCS_STRING);
 
         PROTOCOL_STRING_MAP = new ArrayMap<>();
         PROTOCOL_STRING_MAP.put("IP", PROTOCOL_IP);
@@ -528,6 +567,7 @@
     private final int mCarrierId;
     private final int mSkip464Xlat;
     private final boolean mAlwaysOn;
+    private final @InfrastructureBitmask int mInfrastructureBitmask;
 
     /**
      * Returns the default MTU (Maximum Transmission Unit) size in bytes of the IPv4 routes brought
@@ -916,6 +956,29 @@
         return mAlwaysOn;
     }
 
+    /**
+     * Check if this APN can be used when the device is using certain infrastructure(s).
+     *
+     * @param infrastructures The infrastructure(s) the device is using.
+     *
+     * @return {@code true} if this APN can be used.
+     * @hide
+     */
+    public boolean isForInfrastructure(@InfrastructureBitmask int infrastructures) {
+        return (mInfrastructureBitmask & infrastructures) != 0;
+    }
+
+    /**
+     * @return The infrastructure bitmask of which the APN can be used on. For example, some APNs
+     * can only be used when the device is on cellular, on satellite, or both.
+     *
+     * @hide
+     */
+    @InfrastructureBitmask
+    public int getInfrastructureBitmask() {
+        return mInfrastructureBitmask;
+    }
+
     private ApnSetting(Builder builder) {
         this.mEntryName = builder.mEntryName;
         this.mApnName = builder.mApnName;
@@ -952,6 +1015,7 @@
         this.mCarrierId = builder.mCarrierId;
         this.mSkip464Xlat = builder.mSkip464Xlat;
         this.mAlwaysOn = builder.mAlwaysOn;
+        this.mInfrastructureBitmask = builder.mInfrastructureBitmask;
     }
 
     /**
@@ -1031,6 +1095,8 @@
                         cursor.getColumnIndexOrThrow(Telephony.Carriers.CARRIER_ID)))
                 .setSkip464Xlat(cursor.getInt(cursor.getColumnIndexOrThrow(Carriers.SKIP_464XLAT)))
                 .setAlwaysOn(cursor.getInt(cursor.getColumnIndexOrThrow(Carriers.ALWAYS_ON)) == 1)
+                .setInfrastructureBitmask(cursor.getInt(cursor.getColumnIndexOrThrow(
+                        Telephony.Carriers.INFRASTRUCTURE_BITMASK)))
                 .buildWithoutCheck();
     }
 
@@ -1070,6 +1136,7 @@
                 .setCarrierId(apn.mCarrierId)
                 .setSkip464Xlat(apn.mSkip464Xlat)
                 .setAlwaysOn(apn.mAlwaysOn)
+                .setInfrastructureBitmask(apn.mInfrastructureBitmask)
                 .buildWithoutCheck();
     }
 
@@ -1115,6 +1182,7 @@
         sb.append(", ").append(mCarrierId);
         sb.append(", ").append(mSkip464Xlat);
         sb.append(", ").append(mAlwaysOn);
+        sb.append(", ").append(mInfrastructureBitmask);
         sb.append(", ").append(Objects.hash(mUser, mPassword));
         return sb.toString();
     }
@@ -1179,7 +1247,7 @@
                 mProtocol, mRoamingProtocol, mMtuV4, mMtuV6, mCarrierEnabled, mNetworkTypeBitmask,
                 mLingeringNetworkTypeBitmask, mProfileId, mPersistent, mMaxConns, mWaitTime,
                 mMaxConnsTime, mMvnoType, mMvnoMatchData, mApnSetId, mCarrierId, mSkip464Xlat,
-                mAlwaysOn);
+                mAlwaysOn, mInfrastructureBitmask);
     }
 
     @Override
@@ -1191,36 +1259,37 @@
         ApnSetting other = (ApnSetting) o;
 
         return mEntryName.equals(other.mEntryName)
-                && Objects.equals(mId, other.mId)
+                && mId == other.mId
                 && Objects.equals(mOperatorNumeric, other.mOperatorNumeric)
                 && Objects.equals(mApnName, other.mApnName)
                 && Objects.equals(mProxyAddress, other.mProxyAddress)
                 && Objects.equals(mMmsc, other.mMmsc)
                 && Objects.equals(mMmsProxyAddress, other.mMmsProxyAddress)
-                && Objects.equals(mMmsProxyPort, other.mMmsProxyPort)
-                && Objects.equals(mProxyPort, other.mProxyPort)
+                && mMmsProxyPort == other.mMmsProxyPort
+                && mProxyPort == other.mProxyPort
                 && Objects.equals(mUser, other.mUser)
                 && Objects.equals(mPassword, other.mPassword)
-                && Objects.equals(mAuthType, other.mAuthType)
-                && Objects.equals(mApnTypeBitmask, other.mApnTypeBitmask)
-                && Objects.equals(mProtocol, other.mProtocol)
-                && Objects.equals(mRoamingProtocol, other.mRoamingProtocol)
-                && Objects.equals(mCarrierEnabled, other.mCarrierEnabled)
-                && Objects.equals(mProfileId, other.mProfileId)
-                && Objects.equals(mPersistent, other.mPersistent)
-                && Objects.equals(mMaxConns, other.mMaxConns)
-                && Objects.equals(mWaitTime, other.mWaitTime)
-                && Objects.equals(mMaxConnsTime, other.mMaxConnsTime)
-                && Objects.equals(mMtuV4, other.mMtuV4)
-                && Objects.equals(mMtuV6, other.mMtuV6)
-                && Objects.equals(mMvnoType, other.mMvnoType)
+                && mAuthType == other.mAuthType
+                && mApnTypeBitmask == other.mApnTypeBitmask
+                && mProtocol == other.mProtocol
+                && mRoamingProtocol == other.mRoamingProtocol
+                && mCarrierEnabled == other.mCarrierEnabled
+                && mProfileId == other.mProfileId
+                && mPersistent == other.mPersistent
+                && mMaxConns == other.mMaxConns
+                && mWaitTime == other.mWaitTime
+                && mMaxConnsTime == other.mMaxConnsTime
+                && mMtuV4 == other.mMtuV4
+                && mMtuV6 == other.mMtuV6
+                && mMvnoType == other.mMvnoType
                 && Objects.equals(mMvnoMatchData, other.mMvnoMatchData)
-                && Objects.equals(mNetworkTypeBitmask, other.mNetworkTypeBitmask)
-                && Objects.equals(mLingeringNetworkTypeBitmask, other.mLingeringNetworkTypeBitmask)
-                && Objects.equals(mApnSetId, other.mApnSetId)
-                && Objects.equals(mCarrierId, other.mCarrierId)
-                && Objects.equals(mSkip464Xlat, other.mSkip464Xlat)
-                && Objects.equals(mAlwaysOn, other.mAlwaysOn);
+                && mNetworkTypeBitmask == other.mNetworkTypeBitmask
+                && mLingeringNetworkTypeBitmask == other.mLingeringNetworkTypeBitmask
+                && mApnSetId == other.mApnSetId
+                && mCarrierId == other.mCarrierId
+                && mSkip464Xlat == other.mSkip464Xlat
+                && mAlwaysOn == other.mAlwaysOn
+                && mInfrastructureBitmask == other.mInfrastructureBitmask;
     }
 
     /**
@@ -1270,7 +1339,8 @@
                 && Objects.equals(mApnSetId, other.mApnSetId)
                 && Objects.equals(mCarrierId, other.mCarrierId)
                 && Objects.equals(mSkip464Xlat, other.mSkip464Xlat)
-                && Objects.equals(mAlwaysOn, other.mAlwaysOn);
+                && Objects.equals(mAlwaysOn, other.mAlwaysOn)
+                && Objects.equals(mInfrastructureBitmask, other.mInfrastructureBitmask);
     }
 
     /**
@@ -1307,7 +1377,8 @@
                 && Objects.equals(this.mApnSetId, other.mApnSetId)
                 && Objects.equals(this.mCarrierId, other.mCarrierId)
                 && Objects.equals(this.mSkip464Xlat, other.mSkip464Xlat)
-                && Objects.equals(this.mAlwaysOn, other.mAlwaysOn);
+                && Objects.equals(this.mAlwaysOn, other.mAlwaysOn)
+                && Objects.equals(this.mInfrastructureBitmask, other.mInfrastructureBitmask);
     }
 
     // Equal or one is null.
@@ -1379,6 +1450,7 @@
         apnValue.put(Telephony.Carriers.CARRIER_ID, mCarrierId);
         apnValue.put(Telephony.Carriers.SKIP_464XLAT, mSkip464Xlat);
         apnValue.put(Telephony.Carriers.ALWAYS_ON, mAlwaysOn);
+        apnValue.put(Telephony.Carriers.INFRASTRUCTURE_BITMASK, mInfrastructureBitmask);
         return apnValue;
     }
 
@@ -1651,6 +1723,7 @@
         dest.writeInt(mCarrierId);
         dest.writeInt(mSkip464Xlat);
         dest.writeBoolean(mAlwaysOn);
+        dest.writeInt(mInfrastructureBitmask);
     }
 
     private static ApnSetting readFromParcel(Parcel in) {
@@ -1686,6 +1759,7 @@
                 .setCarrierId(in.readInt())
                 .setSkip464Xlat(in.readInt())
                 .setAlwaysOn(in.readBoolean())
+                .setInfrastructureBitmask(in.readInt())
                 .buildWithoutCheck();
     }
 
@@ -1767,6 +1841,7 @@
         private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
         private int mSkip464Xlat = Carriers.SKIP_464XLAT_DEFAULT;
         private boolean mAlwaysOn;
+        private int mInfrastructureBitmask = INFRASTRUCTURE_CELLULAR;
 
         /**
          * Default constructor for Builder.
@@ -2189,6 +2264,22 @@
         }
 
         /**
+         * Set the infrastructure bitmask.
+         *
+         * @param infrastructureBitmask The infrastructure bitmask of which the APN can be used on.
+         * For example, some APNs can only be used when the device is on cellular, on satellite, or
+         * both.
+         *
+         * @return The builder.
+         * @hide
+         */
+        @NonNull
+        public Builder setInfrastructureBitmask(@InfrastructureBitmask int infrastructureBitmask) {
+            this.mInfrastructureBitmask = infrastructureBitmask;
+            return this;
+        }
+
+        /**
          * Builds {@link ApnSetting} from this builder.
          *
          * @return {@code null} if {@link #setApnName(String)} or {@link #setEntryName(String)}
@@ -2198,7 +2289,7 @@
         public ApnSetting build() {
             if ((mApnTypeBitmask & (TYPE_DEFAULT | TYPE_MMS | TYPE_SUPL | TYPE_DUN | TYPE_HIPRI
                     | TYPE_FOTA | TYPE_IMS | TYPE_CBS | TYPE_IA | TYPE_EMERGENCY | TYPE_MCX
-                    | TYPE_XCAP | TYPE_VSIM | TYPE_BIP | TYPE_ENTERPRISE)) == 0
+                    | TYPE_XCAP | TYPE_VSIM | TYPE_BIP | TYPE_ENTERPRISE | TYPE_RCS)) == 0
                 || TextUtils.isEmpty(mApnName) || TextUtils.isEmpty(mEntryName)) {
                 return null;
             }
diff --git a/telephony/java/android/telephony/data/DataProfile.java b/telephony/java/android/telephony/data/DataProfile.java
index f346b92..88a32d1 100644
--- a/telephony/java/android/telephony/data/DataProfile.java
+++ b/telephony/java/android/telephony/data/DataProfile.java
@@ -431,6 +431,8 @@
                 return ApnSetting.TYPE_VSIM;
             case NetworkCapabilities.NET_CAPABILITY_ENTERPRISE:
                 return ApnSetting.TYPE_ENTERPRISE;
+            case NetworkCapabilities.NET_CAPABILITY_RCS:
+                return ApnSetting.TYPE_RCS;
             default:
                 return ApnSetting.TYPE_NONE;
         }
diff --git a/telephony/java/android/telephony/satellite/INtnSignalStrengthCallback.aidl b/telephony/java/android/telephony/satellite/INtnSignalStrengthCallback.aidl
new file mode 100644
index 0000000..54cab48
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/INtnSignalStrengthCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.telephony.satellite.NtnSignalStrength;
+
+/**
+ * Interface for non-terrestrial signal strength notify callback.
+ * @hide
+ */
+oneway interface INtnSignalStrengthCallback {
+    /**
+     * Called when NTN signal strength changes.
+     * @param ntnSignalStrength The new NTN signal strength.
+     */
+    void onNtnSignalStrengthChanged(in NtnSignalStrength ntnSignalStrength);
+}
diff --git a/telephony/java/android/telephony/satellite/NtnSignalStrength.aidl b/telephony/java/android/telephony/satellite/NtnSignalStrength.aidl
new file mode 100644
index 0000000..a79cb69
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/NtnSignalStrength.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+parcelable NtnSignalStrength;
diff --git a/telephony/java/android/telephony/satellite/NtnSignalStrength.java b/telephony/java/android/telephony/satellite/NtnSignalStrength.java
new file mode 100644
index 0000000..16d7654
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/NtnSignalStrength.java
@@ -0,0 +1,149 @@
+/*
+ * 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.telephony.satellite;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.telephony.flags.Flags;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * NTN signal strength related information.
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+public final class NtnSignalStrength implements Parcelable {
+    /** Non-terrestrial network signal strength is not available. */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public static final int NTN_SIGNAL_STRENGTH_NONE = 0;
+    /** Non-terrestrial network signal strength is poor. */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public static final int NTN_SIGNAL_STRENGTH_POOR = 1;
+    /** Non-terrestrial network signal strength is moderate. */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public static final int NTN_SIGNAL_STRENGTH_MODERATE = 2;
+    /** Non-terrestrial network signal strength is good. */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public static final int NTN_SIGNAL_STRENGTH_GOOD = 3;
+    /** Non-terrestrial network signal strength is great. */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public static final int NTN_SIGNAL_STRENGTH_GREAT = 4;
+    @NtnSignalStrengthLevel private int mLevel;
+
+    /** @hide */
+    @IntDef(prefix = "NTN_SIGNAL_STRENGTH_", value = {
+            NTN_SIGNAL_STRENGTH_NONE,
+            NTN_SIGNAL_STRENGTH_POOR,
+            NTN_SIGNAL_STRENGTH_MODERATE,
+            NTN_SIGNAL_STRENGTH_GOOD,
+            NTN_SIGNAL_STRENGTH_GREAT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface NtnSignalStrengthLevel {}
+
+    /**
+     * Create a parcelable object to inform the current non-terrestrial signal strength
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public NtnSignalStrength(@NtnSignalStrengthLevel int level) {
+        this.mLevel = level;
+    }
+
+    /**
+     * This constructor is used to create a copy of an existing NtnSignalStrength object.
+     */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public NtnSignalStrength(@Nullable NtnSignalStrength source) {
+        this.mLevel = (source == null) ? NTN_SIGNAL_STRENGTH_NONE : source.getLevel();
+    }
+
+    private NtnSignalStrength(Parcel in) {
+        readFromParcel(in);
+    }
+
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    @NtnSignalStrengthLevel public int getLevel() {
+        return mLevel;
+    }
+
+    /**
+     * @return 0
+     */
+    @Override
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * @param out  The Parcel in which the object should be written.
+     * @param flags Additional flags about how the object should be written.
+     *              May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
+     */
+    @Override
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeInt(mLevel);
+    }
+
+    private void readFromParcel(Parcel in) {
+        mLevel = in.readInt();
+    }
+
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    @NonNull public static final Creator<NtnSignalStrength> CREATOR =
+            new Creator<NtnSignalStrength>() {
+                @Override public NtnSignalStrength createFromParcel(Parcel in) {
+                    return new NtnSignalStrength(in);
+                }
+
+                @Override public NtnSignalStrength[] newArray(int size) {
+                    return new NtnSignalStrength[size];
+                }
+            };
+
+    @Override
+    public int hashCode() {
+        return mLevel;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) return true;
+        if (obj == null || getClass() != obj.getClass()) return false;
+
+        NtnSignalStrength that = (NtnSignalStrength) obj;
+        return mLevel == that.mLevel;
+    }
+
+    @Override public String toString() {
+        return "NtnSignalStrength{"
+                + "mLevel=" + mLevel
+                + '}';
+    }
+}
diff --git a/telephony/java/android/telephony/satellite/NtnSignalStrengthCallback.java b/telephony/java/android/telephony/satellite/NtnSignalStrengthCallback.java
new file mode 100644
index 0000000..4b79590
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/NtnSignalStrengthCallback.java
@@ -0,0 +1,39 @@
+/*
+ * 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.telephony.satellite;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import com.android.internal.telephony.flags.Flags;
+
+/**
+ * A callback class for notifying satellite signal strength change.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+public interface NtnSignalStrengthCallback {
+    /**
+     * Called when non-terrestrial network signal strength changes.
+     * @param ntnSignalStrength The new non-terrestrial network signal strength.
+     */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    void onNtnSignalStrengthChanged(@NonNull NtnSignalStrength ntnSignalStrength);
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 7322aeb..21d93bd 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -35,7 +35,9 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyFrameworkInitializer;
+import android.telephony.TelephonyManager;
 
 import com.android.internal.telephony.IIntegerConsumer;
 import com.android.internal.telephony.ITelephony;
@@ -77,6 +79,8 @@
     private static final ConcurrentHashMap<SatelliteTransmissionUpdateCallback,
             ISatelliteTransmissionUpdateCallback> sSatelliteTransmissionUpdateCallbackMap =
             new ConcurrentHashMap<>();
+    private static final ConcurrentHashMap<NtnSignalStrengthCallback, INtnSignalStrengthCallback>
+            sNtnSignalStrengthCallbackMap = new ConcurrentHashMap<>();
 
     private final int mSubId;
 
@@ -192,6 +196,14 @@
     public static final String KEY_SATELLITE_NEXT_VISIBILITY = "satellite_next_visibility";
 
     /**
+     * Bundle key to get the response from
+     * {@link #requestNtnSignalStrength(Executor, OutcomeReceiver)}.
+     * @hide
+     */
+
+    public static final String KEY_NTN_SIGNAL_STRENGTH = "ntn_signal_strength";
+
+    /**
      * The request was successfully processed.
      */
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1866,6 +1878,165 @@
         return new HashSet<>();
     }
 
+    /**
+     * Request to get the signal strength of the satellite connection.
+     *
+     * <p>
+     * Note: This API is specifically designed for OEM enabled satellite connectivity only.
+     * For satellite connectivity enabled using carrier roaming, please refer to
+     * {@link android.telephony.TelephonyCallback.SignalStrengthsListener}, and
+     * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}.
+     * </p>
+     *
+     * @param executor The executor on which the callback will be called.
+     * @param callback The callback object to which the result will be delivered. If the request is
+     * successful, {@link OutcomeReceiver#onResult(Object)} will return an instance of
+     * {@link NtnSignalStrength} with a value of {@link NtnSignalStrength.NtnSignalStrengthLevel}
+     * The {@link NtnSignalStrength#NTN_SIGNAL_STRENGTH_NONE} will be returned if there is no
+     * signal strength data available.
+     * If the request is not successful, {@link OutcomeReceiver#onError(Throwable)} will return a
+     * {@link SatelliteException} with the {@link SatelliteResult}.
+     *
+     * @throws SecurityException if the caller doesn't have required permission.
+     * @throws IllegalStateException if the Telephony process is not currently available or
+     * satellite is not supported.
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    @NonNull
+    public void requestNtnSignalStrength(@NonNull @CallbackExecutor Executor executor,
+            @NonNull OutcomeReceiver<NtnSignalStrength, SatelliteException> callback) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                ResultReceiver receiver = new ResultReceiver(null) {
+                    @Override
+                    protected void onReceiveResult(int resultCode, Bundle resultData) {
+                        if (resultCode == SATELLITE_RESULT_SUCCESS) {
+                            if (resultData.containsKey(KEY_NTN_SIGNAL_STRENGTH)) {
+                                NtnSignalStrength ntnSignalStrength =
+                                        resultData.getParcelable(KEY_NTN_SIGNAL_STRENGTH,
+                                                NtnSignalStrength.class);
+                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                        callback.onResult(ntnSignalStrength)));
+                            } else {
+                                loge("KEY_NTN_SIGNAL_STRENGTH does not exist.");
+                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                        callback.onError(new SatelliteException(
+                                                SATELLITE_RESULT_REQUEST_FAILED))));
+                            }
+                        } else {
+                            executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                    callback.onError(new SatelliteException(resultCode))));
+                        }
+                    }
+                };
+                telephony.requestNtnSignalStrength(mSubId, receiver);
+            } else {
+                throw new IllegalStateException("Telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("requestNtnSignalStrength() RemoteException: " + ex);
+            ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Registers for NTN signal strength changed from satellite modem.
+     *
+     * <p>
+     * Note: This API is specifically designed for OEM enabled satellite connectivity only.
+     * For satellite connectivity enabled using carrier roaming, please refer to
+     * {@link android.telephony.TelephonyCallback.SignalStrengthsListener}, and
+     * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}.
+     * </p>
+     *
+     * @param executor The executor on which the callback will be called.
+     * @param callback The callback to handle the NTN signal strength changed event.
+     *
+     * @return The {@link SatelliteResult} result of the operation.
+     *
+     * @throws SecurityException if the caller doesn't have required permission.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    @SatelliteResult public int registerForNtnSignalStrengthChanged(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull NtnSignalStrengthCallback callback) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                INtnSignalStrengthCallback internalCallback =
+                        new INtnSignalStrengthCallback.Stub() {
+                            @Override
+                            public void onNtnSignalStrengthChanged(
+                                    NtnSignalStrength ntnSignalStrength) {
+                                executor.execute(() -> Binder.withCleanCallingIdentity(
+                                        () -> callback.onNtnSignalStrengthChanged(
+                                                ntnSignalStrength)));
+                            }
+                        };
+                sNtnSignalStrengthCallbackMap.put(callback, internalCallback);
+                return telephony.registerForNtnSignalStrengthChanged(mSubId, internalCallback);
+            } else {
+                throw new IllegalStateException("Telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("registerForNtnSignalStrengthChanged() RemoteException: " + ex);
+            ex.rethrowFromSystemServer();
+        }
+        return SATELLITE_RESULT_REQUEST_FAILED;
+    }
+
+    /**
+     * Unregisters for NTN signal strength changed from satellite modem.
+     * If callback was not registered before, the request will be ignored.
+     *
+     * <p>
+     * Note: This API is specifically designed for OEM enabled satellite connectivity only.
+     * For satellite connectivity enabled using carrier roaming, please refer to
+     * {@link TelephonyManager#unregisterTelephonyCallback(TelephonyCallback)}..
+     * </p>
+     *
+     * @param callback The callback that was passed to
+     * {@link #registerForNtnSignalStrengthChanged(Executor, NtnSignalStrengthCallback)}.
+     *
+     * @throws SecurityException if the caller doesn't have required permission.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public void unregisterForNtnSignalStrengthChanged(@NonNull NtnSignalStrengthCallback callback) {
+        Objects.requireNonNull(callback);
+        INtnSignalStrengthCallback internalCallback =
+                sNtnSignalStrengthCallbackMap.remove(callback);
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                if (internalCallback != null) {
+                    telephony.unregisterForNtnSignalStrengthChanged(mSubId, internalCallback);
+                } else {
+                    loge("unregisterForNtnSignalStrengthChanged: No internal callback.");
+                }
+            } else {
+                throw new IllegalStateException("Telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("unregisterForNtnSignalStrengthChanged() RemoteException: " + ex);
+            ex.rethrowFromSystemServer();
+        }
+
+    }
+
+
     private static ITelephony getITelephony() {
         ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
                 .getTelephonyServiceManager()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt b/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl
similarity index 64%
copy from packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
copy to telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl
index 0803a01..b7712bd 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
+++ b/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl
@@ -12,15 +12,17 @@
  * 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.communal.shared
+package android.telephony.satellite.stub;
 
-import android.appwidget.AppWidgetProviderInfo
+import android.telephony.satellite.NtnSignalStrength;
 
-/** A data class that stores info about an app widget that displays in communal mode. */
-data class CommunalAppWidgetInfo(
-    val providerInfo: AppWidgetProviderInfo,
-    val appWidgetId: Int,
-)
+/**
+ * Consumer pattern for a request that receives the signal strength of non-terrestrial network from
+ * the SatelliteService.
+ * @hide
+ */
+oneway interface INtnSignalStrengthConsumer {
+    void accept(in NtnSignalStrength result);
+}
diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
index 7fda550..6b47db1 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
@@ -16,6 +16,7 @@
 
 package android.telephony.satellite.stub;
 
+import android.telephony.satellite.stub.INtnSignalStrengthConsumer;
 import android.telephony.satellite.stub.ISatelliteCapabilitiesConsumer;
 import android.telephony.satellite.stub.ISatelliteListener;
 import android.telephony.satellite.stub.SatelliteDatagram;
@@ -454,4 +455,44 @@
      */
     void requestIsSatelliteEnabledForCarrier(int simSlot, in IIntegerConsumer resultCallback,
             in IBooleanConsumer callback);
+
+    /**
+     * Request to get the signal strength of the satellite connection.
+     *
+     * @param resultCallback The {@link SatelliteError} result of the operation.
+     * @param callback The callback to handle the NTN signal strength changed event.
+     */
+    void requestSignalStrength(in IIntegerConsumer resultCallback,
+            in INtnSignalStrengthConsumer callback);
+
+    /**
+     * The satellite service should report the NTN signal strength via
+     * ISatelliteListener#onNtnSignalStrengthChanged when the NTN signal strength changes.
+     *
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     */
+     void startSendingNtnSignalStrength(in IIntegerConsumer resultCallback);
+
+    /**
+     * The satellite service should stop reporting NTN signal strength to the framework. This will
+     * be called when device is screen off to save power by not letting signal strength updates to
+     * wake up application processor.
+     *
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     */
+     void stopSendingNtnSignalStrength(in IIntegerConsumer resultCallback);
 }
diff --git a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
index d687162..d44ddfa 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
@@ -16,6 +16,7 @@
 
 package android.telephony.satellite.stub;
 
+import android.telephony.satellite.stub.NtnSignalStrength;
 import android.telephony.satellite.stub.NTRadioTechnology;
 import android.telephony.satellite.stub.PointingInfo;
 import android.telephony.satellite.stub.SatelliteDatagram;
@@ -58,4 +59,10 @@
      * @param state The current satellite modem state.
      */
     void onSatelliteModemStateChanged(in SatelliteModemState state);
+
+    /**
+     * Called when NTN signal strength changes.
+     * @param ntnSignalStrength The new NTN signal strength.
+     */
+    void onNtnSignalStrengthChanged(in NtnSignalStrength ntnSignalStrength);
 }
diff --git a/telephony/java/android/telephony/satellite/stub/NtnSignalStrength.aidl b/telephony/java/android/telephony/satellite/stub/NtnSignalStrength.aidl
new file mode 100644
index 0000000..f489005
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/stub/NtnSignalStrength.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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.telephony.satellite.stub;
+
+import android.telephony.satellite.stub.NtnSignalStrengthLevel;
+
+/**
+ * @hide
+ */
+parcelable NtnSignalStrength {
+    /**
+     * Non-terrestrial signal strength. The value represents the level of signal strength which can
+     * be translated to the number of signal bars.
+     */
+    NtnSignalStrengthLevel signalStrengthLevel;
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt b/telephony/java/android/telephony/satellite/stub/NtnSignalStrengthLevel.aidl
similarity index 67%
copy from packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
copy to telephony/java/android/telephony/satellite/stub/NtnSignalStrengthLevel.aidl
index 0803a01..53b1373 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
+++ b/telephony/java/android/telephony/satellite/stub/NtnSignalStrengthLevel.aidl
@@ -12,15 +12,18 @@
  * 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.communal.shared
+package android.telephony.satellite.stub;
 
-import android.appwidget.AppWidgetProviderInfo
-
-/** A data class that stores info about an app widget that displays in communal mode. */
-data class CommunalAppWidgetInfo(
-    val providerInfo: AppWidgetProviderInfo,
-    val appWidgetId: Int,
-)
+/**
+ * {@hide}
+ */
+@Backing(type="int")
+enum NtnSignalStrengthLevel {
+    NTN_SIGNAL_STRENGTH_NONE = 0,
+    NTN_SIGNAL_STRENGTH_POOR = 1,
+    NTN_SIGNAL_STRENGTH_MODERATE = 2,
+    NTN_SIGNAL_STRENGTH_GOOD = 3,
+    NTN_SIGNAL_STRENGTH_GREAT = 4
+}
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
index 4cee01e..a636a61 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -241,6 +241,30 @@
                     "requestIsSatelliteEnabledForCarrier");
         }
 
+        @Override
+        public void requestSignalStrength(IIntegerConsumer resultCallback,
+                INtnSignalStrengthConsumer callback) throws RemoteException {
+            executeMethodAsync(
+                    () -> SatelliteImplBase.this.requestSignalStrength(resultCallback, callback),
+                    "requestSignalStrength");
+        }
+
+        @Override
+        public void startSendingNtnSignalStrength(IIntegerConsumer resultCallback)
+                throws RemoteException {
+            executeMethodAsync(
+                    () -> SatelliteImplBase.this.startSendingNtnSignalStrength(resultCallback),
+                    "startSendingNtnSignalStrength");
+        }
+
+        @Override
+        public void stopSendingNtnSignalStrength(IIntegerConsumer resultCallback)
+                throws RemoteException {
+            executeMethodAsync(
+                    () -> SatelliteImplBase.this.stopSendingNtnSignalStrength(resultCallback),
+                    "stopSendingNtnSignalStrength");
+        }
+
         // Call the methods with a clean calling identity on the executor and wait indefinitely for
         // the future to return.
         private void executeMethodAsync(Runnable r, String errorLogName) throws RemoteException {
@@ -728,4 +752,35 @@
             @NonNull IIntegerConsumer resultCallback, @NonNull IBooleanConsumer callback) {
         // stub implementation
     }
+
+    /**
+     * Request to get the signal strength of the satellite connection.
+     *
+     * @param resultCallback The {@link SatelliteError} result of the operation.
+     * @param callback The callback to handle the NTN signal strength changed event.
+     */
+    public void requestSignalStrength(@NonNull IIntegerConsumer resultCallback,
+            INtnSignalStrengthConsumer callback) {
+        // stub implementation
+    }
+
+    /**
+     * Requests to deliver signal strength changed events through the
+     * {@link ISatelliteListener#onNtnSignalStrengthChanged(NtnSignalStrength ntnSignalStrength)}
+     * callback.
+     *
+     * @param resultCallback The {@link SatelliteError} result of the operation.
+     */
+    public void startSendingNtnSignalStrength(@NonNull IIntegerConsumer resultCallback) {
+        // stub implementation
+    }
+
+    /**
+     * Requests to stop signal strength changed events
+     *
+     * @param resultCallback The {@link SatelliteError} result of the operation.
+     */
+    public void stopSendingNtnSignalStrength(@NonNull IIntegerConsumer resultCallback){
+        // stub implementation
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 3aa5a5a..58e7026 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -67,10 +67,12 @@
 import android.telephony.ims.aidl.IImsRegistration;
 import android.telephony.ims.aidl.IImsRegistrationCallback;
 import android.telephony.ims.aidl.IRcsConfigCallback;
+import android.telephony.satellite.INtnSignalStrengthCallback;
 import android.telephony.satellite.ISatelliteDatagramCallback;
 import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
 import android.telephony.satellite.ISatelliteProvisionStateCallback;
 import android.telephony.satellite.ISatelliteStateCallback;
+import android.telephony.satellite.NtnSignalStrength;
 import android.telephony.satellite.SatelliteCapabilities;
 import android.telephony.satellite.SatelliteDatagram;
 import com.android.ims.internal.IImsServiceFeatureCallback;
@@ -2837,7 +2839,6 @@
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
     void deprovisionSatelliteService(int subId, in String token, in IIntegerConsumer callback);
 
-
     /**
      * Registers for provision state changed from satellite modem.
      *
@@ -3071,4 +3072,40 @@
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
     int[] getSatelliteAttachRestrictionReasonsForCarrier(int subId);
+
+    /**
+     * Request to get the signal strength of the satellite connection.
+     *
+     * @param subId The subId of the subscription to request for.
+     * @param receiver Result receiver to get the error code of the request and the current signal
+     * strength of the satellite connection.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+    void requestNtnSignalStrength(int subId, in ResultReceiver receiver);
+
+    /**
+     * Registers for NTN signal strength changed from satellite modem.
+     *
+     * @param subId The subId of the subscription to request for.
+     * @param callback The callback to handle the NTN signal strength changed event.
+     *
+     * @return The {@link SatelliteResult} result of the operation.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+    int registerForNtnSignalStrengthChanged(int subId, in INtnSignalStrengthCallback callback);
+
+    /**
+     * Unregisters for NTN signal strength changed from satellite modem.
+     * If callback was not registered before, the request will be ignored.
+     *
+     * @param subId The subId of the subscription to unregister for provision state changed.
+     * @param callback The callback that was passed to
+     * {@link #registerForNtnSignalStrengthChanged(Executor, NtnSignalStrengthCallback)}.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+    void unregisterForNtnSignalStrengthChanged(int subId,
+            in INtnSignalStrengthCallback callback);
 }
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index 82aa85d..f4f2be6 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -53,7 +53,17 @@
 }
 
 filegroup {
-    name: "FlickerTestsAppLaunch-src",
+    name: "FlickerTestsAppLaunchCommon-src",
+    srcs: ["src/**/launch/common/*.kt"],
+}
+
+filegroup {
+    name: "FlickerTestsAppLaunch1-src",
+    srcs: ["src/**/launch/OpenApp*.kt"],
+}
+
+filegroup {
+    name: "FlickerTestsAppLaunch2-src",
     srcs: ["src/**/launch/*.kt"],
 }
 
@@ -119,7 +129,8 @@
     exclude_srcs: [
         ":FlickerTestsAppClose-src",
         ":FlickerTestsIme-src",
-        ":FlickerTestsAppLaunch-src",
+        ":FlickerTestsAppLaunch1-src",
+        ":FlickerTestsAppLaunch2-src",
         ":FlickerTestsQuickswitch-src",
         ":FlickerTestsRotation-src",
         ":FlickerTestsNotification-src",
@@ -162,7 +173,8 @@
     instrumentation_target_package: "com.android.server.wm.flicker.launch",
     srcs: [
         ":FlickerTestsBase-src",
-        ":FlickerTestsAppLaunch-src",
+        ":FlickerTestsAppLaunchCommon-src",
+        ":FlickerTestsAppLaunch2-src",
     ],
     exclude_srcs: [
         ":FlickerTestsActivityEmbedding-src",
@@ -170,6 +182,39 @@
 }
 
 android_test {
+    name: "FlickerTestsAppLaunch1",
+    defaults: ["FlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestAppLaunch.xml"],
+    package_name: "com.android.server.wm.flicker.launch",
+    instrumentation_target_package: "com.android.server.wm.flicker.launch",
+    srcs: [
+        ":FlickerTestsBase-src",
+        ":FlickerTestsAppLaunchCommon-src",
+        ":FlickerTestsAppLaunch1-src",
+    ],
+    exclude_srcs: [
+        ":FlickerTestsActivityEmbedding-src",
+    ],
+}
+
+android_test {
+    name: "FlickerTestsAppLaunch2",
+    defaults: ["FlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestAppLaunch.xml"],
+    package_name: "com.android.server.wm.flicker.launch",
+    instrumentation_target_package: "com.android.server.wm.flicker.launch",
+    srcs: [
+        ":FlickerTestsBase-src",
+        ":FlickerTestsAppLaunchCommon-src",
+        ":FlickerTestsAppLaunch2-src",
+    ],
+    exclude_srcs: [
+        ":FlickerTestsActivityEmbedding-src",
+        ":FlickerTestsAppLaunch1-src",
+    ],
+}
+
+android_test {
     name: "FlickerTestsNotification",
     defaults: ["FlickerTestsDefault"],
     additional_manifests: ["manifests/AndroidManifestNotification.xml"],
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt
index 48d5041..f788efa 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt
@@ -16,13 +16,11 @@
 
 package com.android.server.wm.flicker.launch
 
-import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
 import androidx.test.filters.FlakyTest
+import com.android.server.wm.flicker.launch.common.OpenAppFromIconTransition
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -54,28 +52,7 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 open class OpenAppFromIconColdTest(flicker: LegacyFlickerTest) :
-    OpenAppFromLauncherTransition(flicker) {
-    /** {@inheritDoc} */
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            super.transition(this)
-            setup {
-                if (flicker.scenario.isTablet) {
-                    tapl.setExpectedRotation(flicker.scenario.startRotation.value)
-                } else {
-                    tapl.setExpectedRotation(Rotation.ROTATION_0.value)
-                }
-                RemoveAllTasksButHomeRule.removeAllTasksButHome()
-            }
-            transitions {
-                tapl
-                    .goHome()
-                    .switchToAllApps()
-                    .getAppIcon(testApp.appName)
-                    .launch(testApp.packageName)
-            }
-            teardown { testApp.exit(wmHelper) }
-        }
+    OpenAppFromIconTransition(flicker) {
 
     @FlakyTest(bugId = 240916028)
     @Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt
index f575fcc..d86dc50 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt
@@ -21,6 +21,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import com.android.server.wm.flicker.launch.common.OpenAppFromLauncherTransition
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt
index 93d0520..be07053 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt
@@ -25,6 +25,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
 import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.launch.common.OpenAppFromLauncherTransition
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
index 78b58f4..f66eff9 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
@@ -24,6 +24,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.launch.common.OpenAppFromLauncherTransition
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
index 3f931c4..65214764 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
@@ -27,6 +27,7 @@
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.launch.common.OpenAppFromLockscreenTransition
 import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Ignore
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index b85362a..4d31c28 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -25,6 +25,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.launch.common.OpenAppFromLauncherTransition
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt
index 1bdb6e71..42e34b3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt
@@ -30,6 +30,7 @@
 import android.view.KeyEvent
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.launch.common.OpenAppFromLauncherTransition
 import org.junit.FixMethodOrder
 import org.junit.Ignore
 import org.junit.Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt
deleted file mode 100644
index 3d9c067..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker.launch
-
-import android.platform.test.annotations.Presubmit
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.helpers.TransferSplashscreenAppHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test cold launching an app from launcher
- *
- * To run this test: `atest FlickerTests:OpenTransferSplashscreenAppFromLauncherTransition`
- *
- * Actions:
- * ```
- *     Inherit from OpenAppFromIconColdTest, Launch an app [testApp] with an animated splash screen
- *     by clicking it's icon on all apps, and wait for transfer splash screen complete
- * ```
- *
- * Notes:
- * ```
- *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
- *        are inherited [OpenAppTransition]
- *     2. Verify no flickering when transfer splash screen to app window.
- * ```
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenTransferSplashscreenAppFromLauncherTransition(flicker: LegacyFlickerTest) :
-    OpenAppFromIconColdTest(flicker) {
-    override val testApp = TransferSplashscreenAppHelper(instrumentation)
-
-    /**
-     * Checks that [ComponentNameMatcher.LAUNCHER] window is the top window at the start of the
-     * transition, and is replaced by [ComponentNameMatcher.SPLASH_SCREEN], then [testApp] remains
-     * visible until the end
-     */
-    @Presubmit
-    @Test
-    fun appWindowAfterSplash() {
-        flicker.assertWm {
-            this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
-                .then()
-                .isAppWindowOnTop(ComponentNameMatcher.SPLASH_SCREEN)
-                .then()
-                .isAppWindowOnTop(testApp)
-                .isAppWindowInvisible(ComponentNameMatcher.SPLASH_SCREEN)
-        }
-    }
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromIconTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromIconTransition.kt
new file mode 100644
index 0000000..c854701
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromIconTransition.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.launch.common
+
+import android.tools.common.Rotation
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
+
+abstract class OpenAppFromIconTransition(flicker: LegacyFlickerTest) :
+    OpenAppFromLauncherTransition(flicker) {
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
+            setup {
+                if (flicker.scenario.isTablet) {
+                    tapl.setExpectedRotation(flicker.scenario.startRotation.value)
+                } else {
+                    tapl.setExpectedRotation(Rotation.ROTATION_0.value)
+                }
+                RemoveAllTasksButHomeRule.removeAllTasksButHome()
+            }
+            transitions {
+                tapl
+                    .goHome()
+                    .switchToAllApps()
+                    .getAppIcon(testApp.appName)
+                    .launch(testApp.packageName)
+            }
+            teardown { testApp.exit(wmHelper) }
+        }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLauncherTransition.kt
similarity index 95%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLauncherTransition.kt
index 4fc9bcb..9d7096e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLauncherTransition.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.wm.flicker.launch
+package com.android.server.wm.flicker.launch.common
 
 import android.platform.test.annotations.Presubmit
 import android.tools.common.traces.component.ComponentNameMatcher
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLockscreenTransition.kt
similarity index 97%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLockscreenTransition.kt
index cc501e6..7b08843 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLockscreenTransition.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.wm.flicker.launch
+package com.android.server.wm.flicker.launch.common
 
 import android.platform.test.annotations.Presubmit
 import android.tools.common.traces.component.ComponentNameMatcher
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppTransition.kt
similarity index 98%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppTransition.kt
index bb11be5..989619e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppTransition.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.wm.flicker.launch
+package com.android.server.wm.flicker.launch.common
 
 import android.platform.test.annotations.Presubmit
 import android.tools.common.traces.component.ComponentNameMatcher
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenTransferSplashscreenAppFromLauncherTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenTransferSplashscreenAppFromLauncherTransition.kt
new file mode 100644
index 0000000..2e9620b
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenTransferSplashscreenAppFromLauncherTransition.kt
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.launch.common
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.TransferSplashscreenAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test cold launching an app from launcher
+ *
+ * To run this test: `atest FlickerTests:OpenTransferSplashscreenAppFromLauncherTransition`
+ *
+ * Actions:
+ * ```
+ *     Inherit from OpenAppFromIconColdTest, Launch an app [testApp] with an animated splash screen
+ *     by clicking it's icon on all apps, and wait for transfer splash screen complete
+ * ```
+ *
+ * Notes:
+ * ```
+ *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ *        are inherited [OpenAppTransition]
+ *     2. Verify no flickering when transfer splash screen to app window.
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenTransferSplashscreenAppFromLauncherTransition(flicker: LegacyFlickerTest) :
+    OpenAppFromIconTransition(flicker) {
+    override val testApp = TransferSplashscreenAppHelper(instrumentation)
+
+    /**
+     * Checks that [ComponentNameMatcher.LAUNCHER] window is the top window at the start of the
+     * transition, and is replaced by [ComponentNameMatcher.SPLASH_SCREEN], then [testApp] remains
+     * visible until the end
+     */
+    @Presubmit
+    @Test
+    fun appWindowAfterSplash() {
+        flicker.assertWm {
+            this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
+                .then()
+                .isAppWindowOnTop(ComponentNameMatcher.SPLASH_SCREEN)
+                .then()
+                .isAppWindowOnTop(testApp)
+                .isAppWindowInvisible(ComponentNameMatcher.SPLASH_SCREEN)
+        }
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun focusChanges() {
+        super.focusChanges()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appWindowReplacesLauncherAsTopWindow() {
+        super.appWindowReplacesLauncherAsTopWindow()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appWindowAsTopWindowAtEnd() {
+        super.appWindowAsTopWindowAtEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appWindowBecomesTopWindow() {
+        super.appWindowBecomesTopWindow()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appWindowBecomesVisible() {
+        super.appWindowBecomesVisible()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appWindowIsTopWindowAtEnd() {
+        super.appWindowIsTopWindowAtEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appLayerBecomesVisible() {
+        super.appLayerBecomesVisible()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appLayerReplacesLauncher() {
+        super.appLayerReplacesLauncher()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun cujCompleted() {
+        super.cujCompleted()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun entireScreenCovered() {
+        super.entireScreenCovered()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun navBarLayerIsVisibleAtStartAndEnd() {
+        super.navBarLayerIsVisibleAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() {
+        super.navBarLayerPositionAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun navBarWindowIsAlwaysVisible() {
+        super.navBarWindowIsAlwaysVisible()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun navBarWindowIsVisibleAtStartAndEnd() {
+        super.navBarWindowIsVisibleAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun statusBarLayerIsVisibleAtStartAndEnd() {
+        super.statusBarLayerIsVisibleAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun statusBarLayerPositionAtStartAndEnd() {
+        super.statusBarLayerPositionAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun statusBarWindowIsAlwaysVisible() {
+        super.statusBarWindowIsAlwaysVisible()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun taskBarLayerIsVisibleAtStartAndEnd() {
+        super.taskBarLayerIsVisibleAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun taskBarWindowIsAlwaysVisible() {
+        super.taskBarWindowIsAlwaysVisible()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+    }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FsVerityTest/AndroidTest.xml b/tests/FsVerityTest/AndroidTest.xml
index 49cbde0..d2537f6 100644
--- a/tests/FsVerityTest/AndroidTest.xml
+++ b/tests/FsVerityTest/AndroidTest.xml
@@ -24,7 +24,7 @@
     <!-- This test requires root to write against block device. -->
     <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
 
-    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="test-file-name" value="FsVerityTestApp.apk"/>
         <option name="cleanup-apks" value="true"/>
     </target_preparer>
diff --git a/tools/aapt2/integration-tests/AutoVersionTest/Android.bp b/tools/aapt2/integration-tests/AutoVersionTest/Android.bp
index bfd3508..c901efa 100644
--- a/tools/aapt2/integration-tests/AutoVersionTest/Android.bp
+++ b/tools/aapt2/integration-tests/AutoVersionTest/Android.bp
@@ -26,4 +26,5 @@
 android_test {
     name: "AaptAutoVersionTest",
     sdk_version: "current",
+    use_resource_processor: false,
 }
diff --git a/tools/aapt2/integration-tests/BasicTest/Android.bp b/tools/aapt2/integration-tests/BasicTest/Android.bp
index 7db9d26..d0649ea 100644
--- a/tools/aapt2/integration-tests/BasicTest/Android.bp
+++ b/tools/aapt2/integration-tests/BasicTest/Android.bp
@@ -26,4 +26,5 @@
 android_test {
     name: "AaptBasicTest",
     sdk_version: "current",
+    use_resource_processor: false,
 }
diff --git a/tools/aapt2/integration-tests/StaticLibTest/App/Android.bp b/tools/aapt2/integration-tests/StaticLibTest/App/Android.bp
index 80404ee..ebb4e9f 100644
--- a/tools/aapt2/integration-tests/StaticLibTest/App/Android.bp
+++ b/tools/aapt2/integration-tests/StaticLibTest/App/Android.bp
@@ -24,9 +24,9 @@
 }
 
 android_test {
-
     name: "AaptTestStaticLib_App",
     sdk_version: "current",
+    use_resource_processor: false,
     srcs: ["src/**/*.java"],
     asset_dirs: [
         "assets",
diff --git a/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.bp b/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.bp
index a84da43..ee12a929 100644
--- a/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.bp
+++ b/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.bp
@@ -26,6 +26,7 @@
 android_library {
     name: "AaptTestStaticLib_LibOne",
     sdk_version: "current",
+    use_resource_processor: false,
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
 }
diff --git a/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.bp b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.bp
index d386c3a..83b23624 100644
--- a/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.bp
+++ b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.bp
@@ -26,6 +26,7 @@
 android_library {
     name: "AaptTestStaticLib_LibTwo",
     sdk_version: "current",
+    use_resource_processor: false,
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
     libs: ["AaptTestStaticLib_LibOne"],
diff --git a/tools/aapt2/integration-tests/SymlinkTest/Android.bp b/tools/aapt2/integration-tests/SymlinkTest/Android.bp
index 1e8cf86..15a6a20 100644
--- a/tools/aapt2/integration-tests/SymlinkTest/Android.bp
+++ b/tools/aapt2/integration-tests/SymlinkTest/Android.bp
@@ -26,4 +26,5 @@
 android_test {
     name: "AaptSymlinkTest",
     sdk_version: "current",
+    use_resource_processor: false,
 }
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh b/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh
index 6bf074b..523106f 100755
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh
@@ -36,7 +36,7 @@
 HOSTSTUBGEN=hoststubgen
 
 # Rebuild the tool and the dependencies. These are the only things we build with the build system.
-run m $HOSTSTUBGEN hoststubgen-annotations hoststubgen-helper-runtime truth-prebuilt junit
+run m $HOSTSTUBGEN hoststubgen-annotations hoststubgen-helper-runtime truth junit
 
 
 # Build tiny-framework
@@ -55,7 +55,7 @@
 
 test_compile_classpaths=(
   $SOONG_INT/external/junit/junit/android_common/combined/junit.jar
-  $ANDROID_HOST_OUT/framework/truth-prebuilt.jar
+  $SOONG_INT/external/truth/truth/android_common/combined/truth.jar
 )
 
 test_runtime_classpaths=(
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index 2a199d2..58638e8 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -113,6 +113,7 @@
     private HashMap<String, IPnoScanEvent> mPnoScanEventHandlers = new HashMap<>();
     private HashMap<String, IApInterfaceEventCallback> mApInterfaceListeners = new HashMap<>();
     private Runnable mDeathEventHandler;
+    private Object mLock = new Object();
     /**
      * Ensures that no more than one sendMgmtFrame operation runs concurrently.
      */
@@ -625,13 +626,15 @@
     @VisibleForTesting
     public void binderDied() {
         mEventHandler.post(() -> {
-            Log.e(TAG, "Wificond died!");
-            clearState();
-            // Invalidate the global wificond handle on death. Will be refreshed
-            // on the next setup call.
-            mWificond = null;
-            if (mDeathEventHandler != null) {
-                mDeathEventHandler.run();
+            synchronized (mLock) {
+                Log.e(TAG, "Wificond died!");
+                clearState();
+                // Invalidate the global wificond handle on death. Will be refreshed
+                // on the next setup call.
+                mWificond = null;
+                if (mDeathEventHandler != null) {
+                    mDeathEventHandler.run();
+                }
             }
         });
     }
@@ -867,26 +870,28 @@
     * @return Returns true on success.
     */
     public boolean tearDownInterfaces() {
-        Log.d(TAG, "tearing down interfaces in wificond");
-        // Explicitly refresh the wificodn handler because |tearDownInterfaces()|
-        // could be used to cleanup before we setup any interfaces.
-        if (!retrieveWificondAndRegisterForDeath()) {
+        synchronized (mLock) {
+            Log.d(TAG, "tearing down interfaces in wificond");
+            // Explicitly refresh the wificond handler because |tearDownInterfaces()|
+            // could be used to cleanup before we setup any interfaces.
+            if (!retrieveWificondAndRegisterForDeath()) {
+                return false;
+            }
+
+            try {
+                for (Map.Entry<String, IWifiScannerImpl> entry : mWificondScanners.entrySet()) {
+                    entry.getValue().unsubscribeScanEvents();
+                    entry.getValue().unsubscribePnoScanEvents();
+                }
+                mWificond.tearDownInterfaces();
+                clearState();
+                return true;
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to tear down interfaces due to remote exception");
+            }
+
             return false;
         }
-
-        try {
-            for (Map.Entry<String, IWifiScannerImpl> entry : mWificondScanners.entrySet()) {
-                entry.getValue().unsubscribeScanEvents();
-                entry.getValue().unsubscribePnoScanEvents();
-            }
-            mWificond.tearDownInterfaces();
-            clearState();
-            return true;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to tear down interfaces due to remote exception");
-        }
-
-        return false;
     }
 
     /** Helper function to look up the interface handle using name */
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
index 01f1591..3d5a0f7 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
@@ -16,6 +16,7 @@
 
 package android.net.wifi.sharedconnectivity.app;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -169,6 +170,7 @@
          * @return Returns the Builder object.
          */
         @NonNull
+        @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status")
         public Builder setBatteryCharging(boolean isBatteryCharging) {
             mIsBatteryCharging = isBatteryCharging;
             return this;
@@ -283,6 +285,7 @@
      *
      * @return Returns true if the battery of the remote device is charging.
      */
+    @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status")
     public boolean isBatteryCharging() {
         return mIsBatteryCharging;
     }
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
index 71ac94b..b0f68f7 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
@@ -17,6 +17,7 @@
 package android.net.wifi.sharedconnectivity.app;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -296,6 +297,7 @@
      */
     @TestApi
     @NonNull
+    @FlaggedApi("com.android.wifi.flags.shared_connectivity_broadcast_receiver_test_api")
     public BroadcastReceiver getBroadcastReceiver() {
         return mBroadcastReceiver;
     }