diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 1c9785f34b79f70a506eb7c162029f1b514433f0..c8046ab5bb0e5f0994f0a084251ebe01eb4df978 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -22,6 +22,7 @@ aconfig_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}",
@@ -151,6 +152,11 @@ aconfig_declarations {
 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"],
 }
 
@@ -288,6 +294,12 @@ java_aconfig_library {
     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",
@@ -544,3 +556,16 @@ 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 45bb161840692de730884ea7b522787ec5d8953c..e7adf203334e509c615dfbe626d3986cc02eaa21 100644
--- a/ProtoLibraries.bp
+++ b/ProtoLibraries.bp
@@ -77,6 +77,42 @@ gensrcs {
     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/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index a143d6fd42507ae91118a3fbbccb765c4e63af3e..bbe1485ddcd49d2940256bd2498920da67b4d24c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1555,7 +1555,7 @@ public class JobSchedulerService extends com.android.server.SystemService
 
     private final Predicate<Integer> mIsUidActivePredicate = this::isUidActive;
 
-    public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName,
+    public int scheduleAsPackage(JobInfo job, JobWorkItem work, int callingUid, String packageName,
             int userId, @Nullable String namespace, String tag) {
         // Rate limit excessive schedule() calls.
         final String servicePkg = job.getService().getPackageName();
@@ -1608,12 +1608,12 @@ public class JobSchedulerService extends com.android.server.SystemService
             mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG);
         }
 
-        if (mActivityManagerInternal.isAppStartModeDisabled(uId, servicePkg)) {
-            Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
+        if (mActivityManagerInternal.isAppStartModeDisabled(callingUid, servicePkg)) {
+            Slog.w(TAG, "Not scheduling job for " + callingUid + ":" + job.toString()
                     + " -- package not allowed to start");
             Counter.logIncrementWithUid(
                     "job_scheduler.value_cntr_w_uid_schedule_failure_app_start_mode_disabled",
-                    uId);
+                    callingUid);
             return JobScheduler.RESULT_FAILURE;
         }
 
@@ -1623,7 +1623,7 @@ public class JobSchedulerService extends com.android.server.SystemService
                             job.getEstimatedNetworkDownloadBytes()));
             sInitialJobEstimatedNetworkUploadKBLogger.logSample(
                     safelyScaleBytesToKBForHistogram(job.getEstimatedNetworkUploadBytes()));
-            sJobMinimumChunkKBLogger.logSampleWithUid(uId,
+            sJobMinimumChunkKBLogger.logSampleWithUid(callingUid,
                     safelyScaleBytesToKBForHistogram(job.getMinimumNetworkChunkBytes()));
             if (work != null) {
                 sInitialJwiEstimatedNetworkDownloadKBLogger.logSample(
@@ -1632,7 +1632,7 @@ public class JobSchedulerService extends com.android.server.SystemService
                 sInitialJwiEstimatedNetworkUploadKBLogger.logSample(
                         safelyScaleBytesToKBForHistogram(
                                 work.getEstimatedNetworkUploadBytes()));
-                sJwiMinimumChunkKBLogger.logSampleWithUid(uId,
+                sJwiMinimumChunkKBLogger.logSampleWithUid(callingUid,
                         safelyScaleBytesToKBForHistogram(
                                 work.getMinimumNetworkChunkBytes()));
             }
@@ -1640,11 +1640,12 @@ public class JobSchedulerService extends com.android.server.SystemService
 
         if (work != null) {
             Counter.logIncrementWithUid(
-                    "job_scheduler.value_cntr_w_uid_job_work_items_enqueued", uId);
+                    "job_scheduler.value_cntr_w_uid_job_work_items_enqueued", callingUid);
         }
 
         synchronized (mLock) {
-            final JobStatus toCancel = mJobs.getJobByUidAndJobId(uId, namespace, job.getId());
+            final JobStatus toCancel =
+                    mJobs.getJobByUidAndJobId(callingUid, namespace, job.getId());
 
             if (work != null && toCancel != null) {
                 // Fast path: we are adding work to an existing job, and the JobInfo is not
@@ -1664,7 +1665,7 @@ public class JobSchedulerService extends com.android.server.SystemService
                     // TODO(273758274): improve JobScheduler's resilience and memory management
                     if (toCancel.getWorkCount() >= mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS
                             && toCancel.isPersisted()) {
-                        Slog.w(TAG, "Too many JWIs for uid " + uId);
+                        Slog.w(TAG, "Too many JWIs for uid " + callingUid);
                         throw new IllegalStateException("Apps may not persist more than "
                                 + mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS
                                 + " JobWorkItems per job");
@@ -1682,7 +1683,8 @@ public class JobSchedulerService extends com.android.server.SystemService
                                         | JobStatus.INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ);
                     }
                     mJobs.touchJob(toCancel);
-                    sEnqueuedJwiHighWaterMarkLogger.logSampleWithUid(uId, toCancel.getWorkCount());
+                    sEnqueuedJwiHighWaterMarkLogger
+                            .logSampleWithUid(callingUid, toCancel.getWorkCount());
 
                     // If any of work item is enqueued when the source is in the foreground,
                     // exempt the entire job.
@@ -1692,8 +1694,8 @@ public class JobSchedulerService extends com.android.server.SystemService
                 }
             }
 
-            JobStatus jobStatus =
-                    JobStatus.createFromJobInfo(job, uId, packageName, userId, namespace, tag);
+            JobStatus jobStatus = JobStatus.createFromJobInfo(
+                    job, callingUid, packageName, userId, namespace, tag);
 
             // Return failure early if expedited job quota used up.
             if (jobStatus.isRequestedExpeditedJob()) {
@@ -1702,7 +1704,7 @@ public class JobSchedulerService extends com.android.server.SystemService
                         && !mQuotaController.isWithinEJQuotaLocked(jobStatus))) {
                     Counter.logIncrementWithUid(
                             "job_scheduler.value_cntr_w_uid_schedule_failure_ej_out_of_quota",
-                            uId);
+                            callingUid);
                     return JobScheduler.RESULT_FAILURE;
                 }
             }
@@ -1716,10 +1718,10 @@ public class JobSchedulerService extends com.android.server.SystemService
             if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString());
             // Jobs on behalf of others don't apply to the per-app job cap
             if (packageName == null) {
-                if (mJobs.countJobsForUid(uId) > MAX_JOBS_PER_APP) {
-                    Slog.w(TAG, "Too many jobs for uid " + uId);
+                if (mJobs.countJobsForUid(callingUid) > MAX_JOBS_PER_APP) {
+                    Slog.w(TAG, "Too many jobs for uid " + callingUid);
                     Counter.logIncrementWithUid(
-                            "job_scheduler.value_cntr_w_uid_max_scheduling_limit_hit", uId);
+                            "job_scheduler.value_cntr_w_uid_max_scheduling_limit_hit", callingUid);
                     throw new IllegalStateException("Apps may not schedule more than "
                             + MAX_JOBS_PER_APP + " distinct jobs");
                 }
@@ -1743,7 +1745,7 @@ public class JobSchedulerService extends com.android.server.SystemService
                 // TODO(273758274): improve JobScheduler's resilience and memory management
                 if (work != null && toCancel.isPersisted()
                         && toCancel.getWorkCount() >= mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS) {
-                    Slog.w(TAG, "Too many JWIs for uid " + uId);
+                    Slog.w(TAG, "Too many JWIs for uid " + callingUid);
                     throw new IllegalStateException("Apps may not persist more than "
                             + mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS
                             + " JobWorkItems per job");
@@ -1759,13 +1761,14 @@ public class JobSchedulerService extends com.android.server.SystemService
             if (work != null) {
                 // If work has been supplied, enqueue it into the new job.
                 jobStatus.enqueueWorkLocked(work);
-                sEnqueuedJwiHighWaterMarkLogger.logSampleWithUid(uId, jobStatus.getWorkCount());
+                sEnqueuedJwiHighWaterMarkLogger
+                        .logSampleWithUid(callingUid, jobStatus.getWorkCount());
             }
 
-            final int sourceUid = uId;
+            final int sourceUid = jobStatus.getSourceUid();
             FrameworkStatsLog.write(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED,
                     jobStatus.isProxyJob()
-                            ? new int[]{sourceUid, jobStatus.getUid()} : new int[]{sourceUid},
+                            ? new int[]{sourceUid, callingUid} : new int[]{sourceUid},
                     // Given that the source tag is set by the calling app, it should be connected
                     // to the calling app in the attribution for a proxied job.
                     jobStatus.isProxyJob()
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 d48d84ba6980e8ae4239364718b1360e7a564c01..1fdf906fd4ea7495582e3ae30e11db53360f7433 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.StringJoiner;
 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 @@ public final class JobStore {
     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 @@ public final class JobStore {
             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 e1621008cc33800742309eca7052dcbe97793057..e086bfe5cbb2ca478e92608a7686458f13c4c418 100644
--- a/api/ApiDocs.bp
+++ b/api/ApiDocs.bp
@@ -139,9 +139,22 @@ droidstubs {
 // 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 7e41660cf1a2488d21bc7ca083b99da3cbecd699..7e78185b2659153e4d47fe358340464281d136b3 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -739,7 +739,9 @@ java_api_library {
         "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/core/api/current.txt b/core/api/current.txt
index 66aeb0f7cbafb1ab718f38ad514778cd1b11edf9..7d5417d985f0fa27633bbddb6c8b2f504efb1681 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -668,6 +668,7 @@ package android {
     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 @@ package android.app {
     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 @@ package android.companion {
     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 @@ package android.companion {
     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 @@ package android.companion {
     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;
@@ -11719,7 +11722,7 @@ package android.content.om {
     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 @@ package android.content.pm {
     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 @@ package android.content.pm {
     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 @@ package android.database.sqlite {
   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 @@ package android.database.sqlite {
     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 @@ package android.database.sqlite {
     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 @@ package android.hardware.biometrics {
     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 @@ package android.media {
     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 @@ package android.telephony.data {
     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/system-current.txt b/core/api/system-current.txt
index 5d1f6dc3e675d3ce507c41a497fac6e883d44ab1..ac61107dd75274c9af03ed5a520ee3e2d79babcd 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3149,7 +3149,6 @@ package android.companion {
 
   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 @@ package android.net.wifi.sharedconnectivity.app {
     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 @@ package android.net.wifi.sharedconnectivity.app {
   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 @@ package android.nfc {
     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 @@ package android.os {
     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 @@ package android.telephony.data {
     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";
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 4fb7b6bddaaced0a06f1157ba5c5c6713645f81b..a5010312e764644e3c5daca9e98b0fea18248744 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -251,10 +251,6 @@ UnflaggedApi: android.media.audiopolicy.AudioMixingRule#writeToParcel(android.os
     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 306bdeb84e4d17e0306d6584b9398dca1943352d..03a58bed3389bbb949d52f1ac0c6c13b746523a0 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -851,7 +851,7 @@ package android.companion {
     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);
   }
 
@@ -2117,7 +2117,7 @@ package android.net.wifi.sharedconnectivity.app {
 
   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 @@ package android.os {
   }
 
   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 {
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index 93e39d5f41f7fada46461f39c635d29e6124bf2f..105e7645ac4bd73eddfa2b023db8ec516882829a 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -257,8 +257,6 @@ UnflaggedApi: android.media.soundtrigger.SoundTriggerManager#loadSoundModel(andr
     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 0293f66061c148e8d0efd36d7b0da1101b82cdfa..ddb221f422d9048b42cfe59d6a8f1a6b7fef2122 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -14,6 +14,15 @@ aidl_library {
     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 a538247998e6c3cb4f0265dad36002feeb9e5ef2..08c18c8b7448c34f820f391de912395e1c47bbfb 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -3483,12 +3483,10 @@ class ContextImpl extends Context {
 
         // 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 a3b82e9356734d7c6169424c0f8bd82977c04e66..d7d654672abc2ec9d2af285f4970860512930d69 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -161,12 +161,6 @@ interface IWallpaperManager {
      */
     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.
      *
@@ -252,13 +246,6 @@ interface IWallpaperManager {
      */
     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.
diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java
index 1fdc51687433bd663a790ddac5da09966af36ab5..369a78144fd39017c2ce709b7bf1e527d5288bb9 100644
--- a/core/java/android/app/LocaleConfig.java
+++ b/core/java/android/app/LocaleConfig.java
@@ -16,9 +16,11 @@
 
 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.res.Resources;
 import android.content.res.TypedArray;
@@ -30,6 +32,7 @@ import android.util.AttributeSet;
 import android.util.Slog;
 import android.util.Xml;
 
+import com.android.internal.R;
 import com.android.internal.util.XmlUtils;
 
 import org.xmlpull.v1.XmlPullParserException;
@@ -39,7 +42,7 @@ import java.lang.annotation.Retention;
 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;
@@ -66,6 +69,8 @@ public class LocaleConfig implements Parcelable {
     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;
 
     /**
@@ -193,8 +198,17 @@ public class LocaleConfig implements Parcelable {
         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(
@@ -209,6 +223,15 @@ public class LocaleConfig implements Parcelable {
         }
         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;
+            }
+        }
     }
 
     /**
@@ -223,6 +246,17 @@ public class LocaleConfig implements Parcelable {
         return mLocales;
     }
 
+    /**
+     * 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.
      *
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 1ecb5d33fba2cfa115c952ba8b608fa67fc7e64e..6009c29ae53cedde82383b12e1f08862db7459e5 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -120,9 +120,9 @@ public class ResourcesManager {
     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 @@ public class ResourcesManager {
     }
 
     /**
-     * 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 e7a5b72eafc56e3f4ce1a53c123a45d62493a06a..d660078a9ae79fd6e36010544161c27f5335fe96 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -313,7 +313,6 @@ public class WallpaperManager {
     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 @@ public class WallpaperManager {
     }
 
     /**
+     * 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 @@ public class WallpaperManager {
      */
     @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 @@ public class WallpaperManager {
      */
     @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 @@ public class WallpaperManager {
      */
     @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 class WallpaperManager {
     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) {
@@ -2987,25 +2949,6 @@ public class WallpaperManager {
         return true;
     }
 
-    /**
-     * 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?
      *
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index c145c0255b0aa89bdb7bdcc1e1816d6e2752bde0..f99615fd2ef4f6652977a7f6e8ee7168731cbf4a 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -19,4 +19,11 @@ flag {
   namespace: "enterprise"
   description: "Add feature to track required changes for enabled V2 of auto-capturing of onboarding bug reports."
   bug: "302517677"
-}
\ No newline at end of file
+}
+
+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 f401a7607364f0228250b20f8f77666d20dba393..4f1c65b1e3957935276f5f79e21f4b655a743485 100644
--- a/core/java/android/app/usage/flags.aconfig
+++ b/core/java/android/app/usage/flags.aconfig
@@ -13,3 +13,11 @@ flag {
     description: "Feature flag for the new REPORT_USAGE_STATS permission."
     bug: "296056771"
 }
+
+flag {
+    name: "use_dedicated_handler_thread"
+    namespace: "backstage_power"
+    description: "Flag to use a dedicated thread for usage event process"
+    is_fixed_read_only: true
+    bug: "299336442"
+}
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index 6393c456bdcdcecfa3f46aba0d7d1ccffba31705..161fa799f2f5fdb1c98b43b271567dcb1f7e33a3 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -144,6 +144,7 @@ public final class AssociationInfo implements Parcelable {
      * @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 @@ public final class AssociationInfo implements Parcelable {
     /**
      * @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 @@ public final class AssociationInfo implements Parcelable {
         }
 
         /** @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 dbc67fc5a3a3683777e6b1bee5b11ad781025010..70811bb329ec906c33411e4696ddaa2126071726 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -1435,6 +1435,7 @@ public final class CompanionDeviceManager {
      *                          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");
@@ -1459,6 +1460,7 @@ public final class CompanionDeviceManager {
      *                          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 1b4234b5205ccb5484394f1563e558a844a55b96..4f9c849865fccba9a0f4b3306b66c8dec3984043 100644
--- a/core/java/android/companion/flags.aconfig
+++ b/core/java/android/companion/flags.aconfig
@@ -12,4 +12,11 @@ flag {
     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/content/Intent.java b/core/java/android/content/Intent.java
index bb9cc0bef6717b74a2388c415ee54533b76e0f4c..7b6bad31539af4030708e80f9436ab65954b6da7 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -4291,6 +4291,14 @@ public class Intent implements Parcelable, Cloneable {
     public static final String ACTION_SHOW_BRIGHTNESS_DIALOG =
             "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
diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
index c4547b8acc2b454392540a370262fdaabdab547f..df2d7e70880f31da4aaddb9c1557d25f17677756 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 @@ public class FabricatedOverlay {
      * @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/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 1b60f8ed904f0b87d1459c616d828701d348fa93..b15c9e4fa15bd7bfdc3626d54a77b92a1e27cea6 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1256,8 +1256,10 @@ public abstract class PackageManager {
     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 @@ public abstract class PackageManager {
 
     /**
      * 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 5cc3b92da3056baa05014a2784bc99e3e84cb332..c7790bd96c6285728642f6804e71ebf29ef61d58 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -27,6 +27,8 @@ import android.annotation.PluralsRes;
 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 @@ public class ResourcesImpl {
 
                 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 (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)) {
-                                availableLocales = null;
+                                // 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
index 0c2c0f494257659abfad0d7c32153a512680988f..1b8eb0748737121b919a4f0605ccc86b1412ab7c 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -8,3 +8,10 @@ flag {
     # 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 b003e75b4e50b698bc4a595096380d7a65591171..8a4f678b52f29fc9beac316c27962b8ef00c4e3b 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -701,7 +701,7 @@ public final class SQLiteDatabase extends SQLiteClosable {
      *   }
      * </pre>
      */
-    @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
+    @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
     public void beginTransactionReadOnly() {
         beginTransactionWithListenerReadOnly(null);
     }
@@ -785,7 +785,7 @@ public final class SQLiteDatabase extends SQLiteClosable {
      *   }
      * </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 @@ public final class SQLiteDatabase extends SQLiteClosable {
      * @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 @@ public final class SQLiteDatabase extends SQLiteClosable {
      * @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 @@ public final class SQLiteDatabase extends SQLiteClosable {
      * @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 @@ public final class SQLiteDatabase extends SQLiteClosable {
      * @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 827420f96fedd448b7053d33da98e23e5438adf8..33f602bbd40e7fdc1f1a92f55a21a926205c9c77 100644
--- a/core/java/android/database/sqlite/SQLiteRawStatement.java
+++ b/core/java/android/database/sqlite/SQLiteRawStatement.java
@@ -71,7 +71,7 @@ import java.util.Objects;
  *
  * @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 564df039456b02195d2424bcb18ddd9085d48f5e..62a51236a2e24f9714660d7b39673c4f948169cd 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 1333f0da725f460da2e89e0b52e8cfd35305204a..a9742cb6c084800e1828aede2052cc008a183800 100644
--- a/core/java/android/hardware/HardwareBuffer.aidl
+++ b/core/java/android/hardware/HardwareBuffer.aidl
@@ -16,4 +16,4 @@
 
 package android.hardware;
 
-@JavaOnlyStableParcelable @NdkOnlyStableParcelable parcelable HardwareBuffer ndk_header "android/hardware_buffer_aidl.h";
+@JavaOnlyStableParcelable @NdkOnlyStableParcelable @RustOnlyStableParcelable parcelable HardwareBuffer ndk_header "android/hardware_buffer_aidl.h" rust_type "nativewindow::HardwareBuffer";
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 490ff640885ef60a9187dcc9923b3d1670da2d16..7a43286692c44f5b4725377ffdeb9fe3c095ee23 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -805,7 +805,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
          * 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 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
          * 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 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
          * 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 6ac1efb4983919977294fedb63f6408bf24873b7..39fbe83b6abbd96bd66e4a714bba974ac66351d3 100644
--- a/core/java/android/hardware/biometrics/CryptoObject.java
+++ b/core/java/android/hardware/biometrics/CryptoObject.java
@@ -20,6 +20,7 @@ import static android.hardware.biometrics.Flags.FLAG_ADD_KEY_AGREEMENT_CRYPTO_OB
 
 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 @@ import javax.crypto.Mac;
 /**
  * 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 @@ public class CryptoObject {
         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 @@ public class CryptoObject {
      * 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 @@ public class CryptoObject {
      * 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 @@ public class CryptoObject {
      * 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 @@ public class CryptoObject {
      * @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 @@ public class CryptoObject {
      * 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 8decd50664b3d9e7e36ad61ed562eb21c681e291..2b5f5ee35a265cf741b428c32d8fb6b82c18c42c 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -220,7 +220,7 @@ public final class DisplayManagerGlobal {
 
         registerCallbackIfNeededLocked();
 
-        if (DEBUG || extraLogging()) {
+        if (DEBUG) {
             Log.d(TAG, "getDisplayInfo: displayId=" + displayId + ", info=" + info);
         }
         return info;
@@ -402,7 +402,7 @@ public final class DisplayManagerGlobal {
     }
 
     private void maybeLogAllDisplayListeners() {
-        if (!sExtraDisplayListenerLogging) {
+        if (!extraLogging()) {
             return;
         }
 
@@ -1222,7 +1222,7 @@ public final class DisplayManagerGlobal {
 
         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 @@ public final class DisplayManagerGlobal {
             }
             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 @@ public final class DisplayManagerGlobal {
             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/nfc/Constants.java b/core/java/android/nfc/Constants.java
new file mode 100644
index 0000000000000000000000000000000000000000..f76833063605d9e3e86bda2866ee86903aa19972
--- /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 46586308e3cfa14b0621a8fc54b0517145918bb2..4a7bd3f294584fc6c34dc607754ed69341fbb061 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -24,6 +24,7 @@ import android.annotation.Nullable;
 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.MifareClassic;
 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 @@ public final class NfcAdapter {
         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 4909b0830eebdfdd4709df29685cc6a5ef979240..32c2a1b40530a78483c7617169e26c9535cc0db6 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -26,6 +26,7 @@ import android.content.ComponentName;
 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 @@ public final class CardEmulation {
             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 47ad72fe8d0e57e2c49a8817a09c9eb892a84db8..e8ad303c42084eccf1c35eab674ab1fd0a91a6cf 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 @@ public final class BugreportParams {
      * 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 82cdd280a0f3ca4a27c568171d56705d05f064ce..d7e440b66e13223d8bb1228340edf45dcf7e9cd7 100644
--- a/core/java/android/os/LocaleList.java
+++ b/core/java/android/os/LocaleList.java
@@ -153,21 +153,21 @@ public final class LocaleList implements Parcelable {
 
     /**
      * 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/OomKillRecord.java b/core/java/android/os/OomKillRecord.java
index 151a65fdfaf54d3bcc63bac846d8c0964b0afff5..ca1d49a93defdfaf2c1eae1a0e3c2ffb6e805dbc 100644
--- a/core/java/android/os/OomKillRecord.java
+++ b/core/java/android/os/OomKillRecord.java
@@ -15,10 +15,15 @@
  */
 package android.os;
 
+import com.android.internal.util.FrameworkStatsLog;
 
 /**
+ * Activity manager communication with kernel out-of-memory (OOM) data handling
+ * and statsd atom logging.
+ *
  * Expected data to get back from the OOM event's file.
- * Note that this should be equivalent to the struct <b>OomKill</b> inside
+ * Note that this class fields' should be equivalent to the struct
+ * <b>OomKill</b> inside
  * <pre>
  * system/memory/libmeminfo/libmemevents/include/memevents.h
  * </pre>
@@ -41,6 +46,18 @@ public final class OomKillRecord {
         this.mOomScoreAdj = oomScoreAdj;
     }
 
+    /**
+     * Logs the event when the kernel OOM killer claims a victims to reduce
+     * memory pressure.
+     * KernelOomKillOccurred = 754
+     */
+    public void logKillOccurred() {
+        FrameworkStatsLog.write(
+                FrameworkStatsLog.KERNEL_OOM_KILL_OCCURRED,
+                mUid, mPid, mOomScoreAdj, mTimeStampInMillis,
+                mProcessName);
+    }
+
     public long getTimestampMilli() {
         return mTimeStampInMillis;
     }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 4c8ef97a7437a868b5f7a5dfb811e9f01a312b17..9034ff10286b8f7da2cdf133db5c81ba81b39d92 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -3330,7 +3330,10 @@ public class UserManager {
      *
      * @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 @@ public class UserManager {
      * @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/flags.aconfig b/core/java/android/os/flags.aconfig
index b7f2e065bac81381976c14c994c2cbf2452bcef8..c4521c036329d8db614b5c43df1ac972aeadb543 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -27,3 +27,10 @@ flag {
     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/provider/Telephony.java b/core/java/android/provider/Telephony.java
index a39157103f716ae066345e8a87e2584c9be9e7bc..27ad45de69e60c7fb0d925d7de1b6157e8945670 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -3195,6 +3195,16 @@ public final class Telephony {
          */
         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)}.
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index c82a4cabaddd3093eb3da0d26001636c71036f39..2a4cbaf79a75e71aab47375e6abfb15cee268eab 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -28,8 +28,6 @@ import android.system.OsConstants;
 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 @@ public class NotificationRankingUpdate implements Parcelable {
      * @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 @@ public class NotificationRankingUpdate implements Parcelable {
      */
     @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 0000000000000000000000000000000000000000..293143595b5e5b1c845ba4f882c2935827115d6e
--- /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/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 2f3d73a3642566cb71c34d01375cd48861b59eba..42a0c9a9cfe9c26a8b9e4f300cead5de07b6af02 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -70,6 +70,7 @@ import android.os.Parcel;
 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 @@ public final class SurfaceControl implements Parcelable {
         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 @@ public final class SurfaceControl implements Parcelable {
             if (mCloseGuard != null) {
                 mCloseGuard.warnIfOpen();
             }
-            removeFromRegistry();
+            SurfaceControlRegistry.getProcessInstance().remove(this);
         } finally {
             super.finalize();
         }
@@ -1519,6 +1520,10 @@ public final class SurfaceControl implements Parcelable {
      */
     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 @@ public final class SurfaceControl implements Parcelable {
                     mChoreographer = null;
                 }
             }
-            removeFromRegistry();
+            SurfaceControlRegistry.getProcessInstance().remove(this);
         }
     }
 
@@ -2765,8 +2770,10 @@ public final class SurfaceControl implements Parcelable {
 
         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 @@ public final class SurfaceControl implements Parcelable {
             applyResizedSurfaces();
             notifyReparentedSurfaces();
             nativeApplyTransaction(mNativeObject, sync, oneWay);
+
+            if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+                SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+                        "apply", this, null, null);
+            }
         }
 
         /**
@@ -2920,6 +2932,10 @@ public final class SurfaceControl implements Parcelable {
         @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 @@ public final class SurfaceControl implements Parcelable {
         @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 @@ public final class SurfaceControl implements Parcelable {
         @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 @@ public final class SurfaceControl implements Parcelable {
             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 final class SurfaceControl implements Parcelable {
         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 final class SurfaceControl implements Parcelable {
         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 @@ public final class SurfaceControl implements Parcelable {
         @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 final class SurfaceControl implements Parcelable {
         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 final class SurfaceControl implements Parcelable {
          */
         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 final class SurfaceControl implements Parcelable {
          */
         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 final class SurfaceControl implements Parcelable {
         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 final class SurfaceControl implements Parcelable {
         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 @@ public final class SurfaceControl implements Parcelable {
         @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 final class SurfaceControl implements Parcelable {
          */
         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 final class SurfaceControl implements Parcelable {
          */
         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 @@ public final class SurfaceControl implements Parcelable {
         @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 final class SurfaceControl implements Parcelable {
          */
         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 final class SurfaceControl implements Parcelable {
         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 @@ public final class SurfaceControl implements Parcelable {
         @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 final class SurfaceControl implements Parcelable {
         */
         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 final class SurfaceControl implements Parcelable {
          */
         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 @@ public final class SurfaceControl implements Parcelable {
         @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 final class SurfaceControl implements Parcelable {
           */
         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 @@ public final class SurfaceControl implements Parcelable {
         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 0f35048cac673741473de333bf6352cc7ec4f86d..52be8f6a76fd4a937169989fdb39dd071fe5b33e 100644
--- a/core/java/android/view/SurfaceControlRegistry.java
+++ b/core/java/android/view/SurfaceControlRegistry.java
@@ -23,7 +23,9 @@ import android.annotation.NonNull;
 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;
@@ -104,6 +106,9 @@ public class SurfaceControlRegistry {
     // 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();
 
@@ -113,6 +118,22 @@ public class SurfaceControlRegistry {
     // 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;
@@ -160,6 +181,12 @@ public class SurfaceControlRegistry {
         }
     }
 
+    @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.
@@ -196,11 +223,9 @@ public class SurfaceControlRegistry {
      * 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;
         }
     }
 
@@ -247,6 +272,91 @@ public class SurfaceControlRegistry {
         }
     }
 
+    /**
+     * 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.
@@ -272,7 +382,27 @@ public class SurfaceControlRegistry {
         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 310bdd5819baee80fcf4bfe1f2ce98c5b9b4868c..4a10cab4ffe46cd0bd9002611de1dfd74d575e65 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -655,6 +655,10 @@ public final class ViewRootImpl implements ViewParent,
      */
     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 @@ public final class ViewRootImpl implements ViewParent,
         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 @@ public final class ViewRootImpl implements ViewParent,
     }
 
     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 @@ public final class ViewRootImpl implements ViewParent,
                     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 @@ public final class ViewRootImpl implements ViewParent,
                 || 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 @@ public final class ViewRootImpl implements ViewParent,
         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 @@ public final class ViewRootImpl implements ViewParent,
         }
 
         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 @@ public final class ViewRootImpl implements ViewParent,
             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 @@ public final class ViewRootImpl implements ViewParent,
                         mPendingTransaction, mLastPerformDrawSkippedReason);
             }
         }
+        mWasLastDrawCanceled = cancelAndRedraw;
+        mLastTraversalWasVisible = isViewVisible;
 
         if (mAttachInfo.mContentCaptureEvents != null) {
             notifyContentCaptureEvents();
@@ -4687,10 +4715,7 @@ public final class ViewRootImpl implements ViewParent,
 
                 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 @@ public final class ViewRootImpl implements ViewParent,
         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 @@ public final class ViewRootImpl implements ViewParent,
     @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);
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index cc612ed93b2f157b9477d4ad43f96489e2857d86..6888b50bb74438d462ac161fcf43e93913d4cce6 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 {
 }
 
 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/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 103725da9af032a445c480ba47d4e4aa4c5945be..f19a2f961391b8578f789a85ad45ff3d3b94dc55 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -685,8 +685,9 @@ public class RemoteViews implements Parcelable, Filter {
             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 @@ public class RemoteViews implements Parcelable, Filter {
     }
 
     /**
-     * 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 class RemoteViews implements Parcelable, Filter {
         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 class RemoteViews implements Parcelable, Filter {
         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 class RemoteViews implements Parcelable, Filter {
         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 class RemoteViews implements Parcelable, Filter {
         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 class RemoteViews implements Parcelable, Filter {
         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 @@ public class RemoteViews implements Parcelable, Filter {
                     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 @@ public class RemoteViews implements Parcelable, Filter {
         }
 
         @Override
-        public final void visitUris(@NonNull Consumer<Uri> visitor) {
+        public void visitUris(@NonNull Consumer<Uri> visitor) {
             mNestedViews.visitUris(visitor);
         }
     }
@@ -7262,6 +7296,15 @@ public class RemoteViews implements Parcelable, Filter {
                         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 17c82b63c443a20799002d69c16099081b83e8cf..a0628c4b95804d2ee043d2e0c217e45a4b1339d5 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -9854,7 +9854,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                 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);
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 7cfd35b075473ff17995b5829d4e42e65647e1e6..79b3b4f686b40383a26626edc4a3fdb0ca520efc 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -7,6 +7,14 @@ flag {
   bug: "232195501"
 }
 
+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"
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index b1d22e069d9d95c2dac2eed64aa514312d5bd385..77e150239803c1364b319b042cbed4f9098a97b2 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 class SystemUiSystemPropertiesFlags {
         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/proto/android/nfc/Android.bp b/core/proto/android/nfc/Android.bp
deleted file mode 100644
index 6a62c917f2405698fde7c2e8bddb430c44415244..0000000000000000000000000000000000000000
--- 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/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 04fd70a96201b4d03f259a0e567b7a63e3573ec9..3496994fe1738f8c330605391146b375e62ed06a 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/public-staging.xml b/core/res/res/values/public-staging.xml
index adc7fe0922aab2617f66d6f3d16114c06e130bac..4cd4f638e191ba044e2681124180657a87ad2954 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/tests/coretests/src/android/os/LocaleListTest.java b/core/tests/coretests/src/android/os/LocaleListTest.java
index 1f00a7a12c542e2ec2035a556f9bca0a6b5b8c5c..88fc8267f5b2b3a6f19be4dd7ef6b55f1aca9283 100644
--- a/core/tests/coretests/src/android/os/LocaleListTest.java
+++ b/core/tests/coretests/src/android/os/LocaleListTest.java
@@ -81,4 +81,49 @@ public class LocaleListTest extends TestCase {
         // 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 517aeae53784e135a24e2bd6b42f45887b7efcd3..0855268411ebceeb4e61f536c38bfd61d124ebca 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.U
 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.content.pm.ShortcutInfo;
 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 @@ public class NotificationRankingUpdateTest {
 
     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 @@ public class NotificationRankingUpdateTest {
         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 @@ public class NotificationRankingUpdateTest {
         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 @@ public class NotificationRankingUpdateTest {
 
     @Test
     public void testRankingUpdate_writesSmartActionToParcel() {
-        if (!mRankingUpdateAshmem) {
+        if (!Flags.rankingUpdateAshmem()) {
             return;
         }
         ArrayList<Notification.Action> actions = new ArrayList<>();
@@ -674,7 +651,7 @@ public class NotificationRankingUpdateTest {
 
     @Test
     public void testRankingUpdate_handlesEmptySmartActionList() {
-        if (!mRankingUpdateAshmem) {
+        if (!Flags.rankingUpdateAshmem()) {
             return;
         }
         ArrayList<Notification.Action> actions = new ArrayList<>();
@@ -697,7 +674,7 @@ public class NotificationRankingUpdateTest {
 
     @Test
     public void testRankingUpdate_handlesNullSmartActionList() {
-        if (!mRankingUpdateAshmem) {
+        if (!Flags.rankingUpdateAshmem()) {
             return;
         }
         NotificationListenerService.Ranking ranking =
diff --git a/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java b/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java
index e117051ba9deb481bbe120ccd8aa25213e62bf16..71bdce4ecb0ea3e37cf89d29246b57f2c4d5df65 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 androidx.test.platform.app.InstrumentationRegistry.getInstrumentat
 
 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 @@ public class SurfaceControlRegistryTests {
         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/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index b71aaf3fc2e679d2e4f359f2d882c50369280afc..cc73ecee2146574c6a209fea2aa620eee4d09ca8 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 e030dad6bf14680baac1180c12ae134b38f75320..9a0a22a08a0c1dc4c1ef3776076bdf96f60fa147 100644
--- a/graphics/java/android/framework_graphics.aconfig
+++ b/graphics/java/android/framework_graphics.aconfig
@@ -2,7 +2,7 @@ package: "com.android.graphics.flags"
 
 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/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java
new file mode 100644
index 0000000000000000000000000000000000000000..ff49cdcab349e7d79127a46611555febd607c5b2
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java
@@ -0,0 +1,119 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import static java.util.Objects.requireNonNull;
+
+import android.graphics.Rect;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+/**
+ * The parameter to create an overlay container that retrieved from
+ * {@link android.app.ActivityOptions} bundle.
+ */
+class OverlayCreateParams {
+
+    // TODO(b/295803704): Move them to WM Extensions so that we can reuse in WM Jetpack.
+    @VisibleForTesting
+    static final String KEY_OVERLAY_CREATE_PARAMS =
+            "androidx.window.extensions.OverlayCreateParams";
+
+    @VisibleForTesting
+    static final String KEY_OVERLAY_CREATE_PARAMS_TASK_ID =
+            "androidx.window.extensions.OverlayCreateParams.taskId";
+
+    @VisibleForTesting
+    static final String KEY_OVERLAY_CREATE_PARAMS_TAG =
+            "androidx.window.extensions.OverlayCreateParams.tag";
+
+    @VisibleForTesting
+    static final String KEY_OVERLAY_CREATE_PARAMS_BOUNDS =
+            "androidx.window.extensions.OverlayCreateParams.bounds";
+
+    private final int mTaskId;
+
+    @NonNull
+    private final String mTag;
+
+    @NonNull
+    private final Rect mBounds;
+
+    OverlayCreateParams(int taskId, @NonNull String tag, @NonNull Rect bounds) {
+        mTaskId = taskId;
+        mTag = requireNonNull(tag);
+        mBounds = requireNonNull(bounds);
+    }
+
+    int getTaskId() {
+        return mTaskId;
+    }
+
+    @NonNull
+    String getTag() {
+        return mTag;
+    }
+
+    @NonNull
+    Rect getBounds() {
+        return mBounds;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mTaskId;
+        result = 31 * result + mTag.hashCode();
+        result = 31 * result + mBounds.hashCode();
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) return true;
+        if (!(obj instanceof OverlayCreateParams thatParams)) return false;
+        return mTaskId == thatParams.mTaskId
+                && mTag.equals(thatParams.mTag)
+                && mBounds.equals(thatParams.mBounds);
+    }
+
+    @Override
+    public String toString() {
+        return OverlayCreateParams.class.getSimpleName() + ": {"
+                + "taskId=" + mTaskId
+                + ", tag=" + mTag
+                + ", bounds=" + mBounds
+                + "}";
+    }
+
+    /** Retrieves the {@link OverlayCreateParams} from {@link android.app.ActivityOptions} bundle */
+    @Nullable
+    static OverlayCreateParams fromBundle(@NonNull Bundle bundle) {
+        final Bundle paramsBundle = bundle.getBundle(KEY_OVERLAY_CREATE_PARAMS);
+        if (paramsBundle == null) {
+            return null;
+        }
+        final int taskId = paramsBundle.getInt(KEY_OVERLAY_CREATE_PARAMS_TASK_ID);
+        final String tag = requireNonNull(paramsBundle.getString(KEY_OVERLAY_CREATE_PARAMS_TAG));
+        final Rect bounds = requireNonNull(paramsBundle.getParcelable(
+                KEY_OVERLAY_CREATE_PARAMS_BOUNDS, Rect.class));
+
+        return new OverlayCreateParams(taskId, tag, bounds);
+    }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index cdfc4c87d27182e23ad3984b468eab205c28424c..2f1eec15e4155291004d85b976df0b526a2d78b1 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -40,9 +40,10 @@ import static androidx.window.extensions.embedding.SplitContainer.isStickyPlaceh
 import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenAdjacent;
 import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked;
 import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO;
+import static androidx.window.extensions.embedding.SplitPresenter.boundsSmallerThanMinDimensions;
 import static androidx.window.extensions.embedding.SplitPresenter.getActivitiesMinDimensionsPair;
 import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair;
-import static androidx.window.extensions.embedding.SplitPresenter.getTaskWindowMetrics;
+import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions;
 import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit;
 
 import android.app.Activity;
@@ -87,6 +88,7 @@ import androidx.window.extensions.embedding.TransactionManager.TransactionRecord
 import androidx.window.extensions.layout.WindowLayoutComponentImpl;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -123,8 +125,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
      * and unregistered via {@link #clearSplitAttributesCalculator()}.
      * This is called when:
      * <ul>
-     *   <li>{@link SplitPresenter#updateSplitContainer(SplitContainer, TaskFragmentContainer,
-     *     WindowContainerTransaction)}</li>
+     *   <li>{@link SplitPresenter#updateSplitContainer}</li>
      *   <li>There's a started Activity which matches {@link SplitPairRule} </li>
      *   <li>Checking whether the place holder should be launched if there's a Activity matches
      *   {@link SplitPlaceholderRule} </li>
@@ -759,6 +760,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
         if (targetContainer == null) {
             // When there is no embedding rule matched, try to place it in the top container
             // like a normal launch.
+            // TODO(b/301034784): Check if it makes sense to place the activity in overlay
+            //  container.
             targetContainer = taskContainer.getTopNonFinishingTaskFragmentContainer();
         }
         if (targetContainer == null) {
@@ -1007,6 +1010,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
         if (taskContainer == null) {
             return;
         }
+        // TODO(b/301034784): Check if it makes sense to place the activity in overlay container.
         final TaskFragmentContainer targetContainer =
                 taskContainer.getTopNonFinishingTaskFragmentContainer();
         if (targetContainer == null) {
@@ -1166,7 +1170,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
                 getActivitiesMinDimensionsPair(primaryActivity, secondaryActivity));
         if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer()
                 && canReuseContainer(splitRule, splitContainer.getSplitRule(),
-                        getTaskWindowMetrics(taskProperties.getConfiguration()),
+                        taskProperties.getTaskMetrics(),
                         calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes())) {
             // Can launch in the existing secondary container if the rules share the same
             // presentation.
@@ -1408,6 +1412,22 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
     private TaskFragmentContainer createEmptyExpandedContainer(
             @NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
             @Nullable Activity launchingActivity) {
+        return createEmptyContainer(wct, intent, taskId, new Rect(), launchingActivity,
+                null /* overlayTag */);
+    }
+
+    /**
+     * Returns an empty {@link TaskFragmentContainer} that we can launch an activity into.
+     * If {@code overlayTag} is set, it means the created {@link TaskFragmentContainer} is an
+     * overlay container.
+     */
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    @Nullable
+    TaskFragmentContainer createEmptyContainer(
+            @NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
+            @NonNull Rect bounds, @Nullable Activity launchingActivity,
+            @Nullable String overlayTag) {
         // We need an activity in the organizer process in the same Task to use as the owner
         // activity, as well as to get the Task window info.
         final Activity activityInTask;
@@ -1423,13 +1443,46 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
             // Can't find any activity in the Task that we can use as the owner activity.
             return null;
         }
-        final TaskFragmentContainer expandedContainer = newContainer(intent, activityInTask,
-                taskId);
-        mPresenter.createTaskFragment(wct, expandedContainer.getTaskFragmentToken(),
-                activityInTask.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED);
-        mPresenter.updateAnimationParams(wct, expandedContainer.getTaskFragmentToken(),
+        final TaskFragmentContainer container = newContainer(null /* pendingAppearedActivity */,
+                intent, activityInTask, taskId, null /* pairedPrimaryContainer*/, overlayTag);
+        final IBinder taskFragmentToken = container.getTaskFragmentToken();
+        // Note that taskContainer will not exist before calling #newContainer if the container
+        // is the first embedded TF in the task.
+        final TaskContainer taskContainer = container.getTaskContainer();
+        final Rect taskBounds = taskContainer.getTaskProperties().getTaskMetrics().getBounds();
+        final Rect sanitizedBounds = sanitizeBounds(bounds, intent, taskBounds);
+        final int windowingMode = taskContainer
+                .getWindowingModeForSplitTaskFragment(sanitizedBounds);
+        mPresenter.createTaskFragment(wct, taskFragmentToken, activityInTask.getActivityToken(),
+                sanitizedBounds, windowingMode);
+        mPresenter.updateAnimationParams(wct, taskFragmentToken,
                 TaskFragmentAnimationParams.DEFAULT);
-        return expandedContainer;
+        mPresenter.setTaskFragmentIsolatedNavigation(wct, taskFragmentToken,
+                overlayTag != null && !sanitizedBounds.isEmpty());
+
+        return container;
+    }
+
+    /**
+     * Returns the expanded bounds if the {@code bounds} violate minimum dimension or are not fully
+     * covered by the task bounds. Otherwise, returns {@code bounds}.
+     */
+    @NonNull
+    private static Rect sanitizeBounds(@NonNull Rect bounds, @NonNull Intent intent,
+                                       @NonNull Rect taskBounds) {
+        if (bounds.isEmpty()) {
+            // Don't need to check if the bounds follows the task bounds.
+            return bounds;
+        }
+        if (boundsSmallerThanMinDimensions(bounds, getMinDimensions(intent))) {
+            // Expand the bounds if the bounds are smaller than minimum dimensions.
+            return new Rect();
+        }
+        if (!taskBounds.contains(bounds)) {
+            // Expand the bounds if the bounds exceed the task bounds.
+            return new Rect();
+        }
+        return bounds;
     }
 
     /**
@@ -1449,8 +1502,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
         final SplitContainer splitContainer = getActiveSplitForContainer(existingContainer);
         final TaskContainer.TaskProperties taskProperties = mPresenter
                 .getTaskProperties(primaryActivity);
-        final WindowMetrics taskWindowMetrics = getTaskWindowMetrics(
-                taskProperties.getConfiguration());
+        final WindowMetrics taskWindowMetrics = taskProperties.getTaskMetrics();
         final SplitAttributes calculatedSplitAttributes = mPresenter.computeSplitAttributes(
                 taskProperties, splitRule, splitRule.getDefaultSplitAttributes(),
                 getActivityIntentMinDimensionsPair(primaryActivity, intent));
@@ -1519,14 +1571,22 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
     TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity,
             @NonNull Activity activityInTask, int taskId) {
         return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */,
-                activityInTask, taskId, null /* pairedPrimaryContainer */);
+                activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */);
     }
 
     @GuardedBy("mLock")
     TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent,
             @NonNull Activity activityInTask, int taskId) {
         return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
-                activityInTask, taskId, null /* pairedPrimaryContainer */);
+                activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */);
+    }
+
+    @GuardedBy("mLock")
+    TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent,
+                                       @NonNull Activity activityInTask, int taskId,
+                                       @NonNull TaskFragmentContainer pairedPrimaryContainer) {
+        return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
+                activityInTask, taskId, pairedPrimaryContainer, null /* tag */);
     }
 
     /**
@@ -1540,11 +1600,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
      * @param taskId                    parent Task of the new TaskFragment.
      * @param pairedPrimaryContainer    the paired primary {@link TaskFragmentContainer}. When it is
      *                                  set, the new container will be added right above it.
+     * @param overlayTag                The tag for the new created overlay container. It must be
+     *                                  needed if {@code isOverlay} is {@code true}. Otherwise,
+     *                                  it should be {@code null}.
      */
     @GuardedBy("mLock")
     TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity,
             @Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId,
-            @Nullable TaskFragmentContainer pairedPrimaryContainer) {
+            @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag) {
         if (activityInTask == null) {
             throw new IllegalArgumentException("activityInTask must not be null,");
         }
@@ -1553,7 +1616,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
         }
         final TaskContainer taskContainer = mTaskContainers.get(taskId);
         final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity,
-                pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer);
+                pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer, overlayTag);
         return container;
     }
 
@@ -1754,13 +1817,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
      * Updates {@link SplitContainer} with the given {@link SplitAttributes} if the
      * {@link SplitContainer} is the top most and not finished. If passed {@link SplitAttributes}
      * are {@code null}, the {@link SplitAttributes} will be calculated with
-     * {@link SplitPresenter#computeSplitAttributes(TaskContainer.TaskProperties, SplitRule, Pair)}.
+     * {@link SplitPresenter#computeSplitAttributes}.
      *
      * @param splitContainer The {@link SplitContainer} to update
      * @param splitAttributes Update with this {@code splitAttributes} if it is not {@code null}.
      *                        Otherwise, use the value calculated by
-     *                        {@link SplitPresenter#computeSplitAttributes(
-     *                        TaskContainer.TaskProperties, SplitRule, Pair)}
+     *                        {@link SplitPresenter#computeSplitAttributes}
      *
      * @return {@code true} if the update succeed. Otherwise, returns {@code false}.
      */
@@ -2255,6 +2317,96 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
         return shouldRetainAssociatedContainer(finishingContainer, associatedContainer);
     }
 
+    /**
+     * Gets all overlay containers from all tasks in this process, or an empty list if there's
+     * no overlay container.
+     * <p>
+     * Note that we only support one overlay container for each task, but an app could have multiple
+     * tasks.
+     */
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    @NonNull
+    List<TaskFragmentContainer> getAllOverlayTaskFragmentContainers() {
+        final List<TaskFragmentContainer> overlayContainers = new ArrayList<>();
+        for (int i = 0; i < mTaskContainers.size(); i++) {
+            final TaskContainer taskContainer = mTaskContainers.valueAt(i);
+            final TaskFragmentContainer overlayContainer = taskContainer.getOverlayContainer();
+            if (overlayContainer != null) {
+                overlayContainers.add(overlayContainer);
+            }
+        }
+        return overlayContainers;
+    }
+
+    @VisibleForTesting
+    // Suppress GuardedBy warning because lint ask to mark this method as
+    // @GuardedBy(container.mController.mLock), which is mLock itself
+    @SuppressWarnings("GuardedBy")
+    @GuardedBy("mLock")
+    @Nullable
+    TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(
+            @NonNull WindowContainerTransaction wct,
+            @NonNull OverlayCreateParams overlayCreateParams, int launchTaskId,
+            @NonNull Intent intent, @NonNull Activity launchActivity) {
+        final int taskId = overlayCreateParams.getTaskId();
+        if (taskId != launchTaskId) {
+            // The task ID doesn't match the launch activity's. Cannot determine the host task
+            // to launch the overlay.
+            throw new IllegalArgumentException("The task ID of "
+                    + "OverlayCreateParams#launchingActivity must match the task ID of "
+                    + "the activity to #startActivity with the activity options that takes "
+                    + "OverlayCreateParams.");
+        }
+        final List<TaskFragmentContainer> overlayContainers =
+                getAllOverlayTaskFragmentContainers();
+        final String overlayTag = overlayCreateParams.getTag();
+
+        // If the requested bounds of OverlayCreateParams are smaller than minimum dimensions
+        // specified by Intent, expand the overlay container to fill the parent task instead.
+        final Rect bounds = overlayCreateParams.getBounds();
+        final Size minDimensions = getMinDimensions(intent);
+        final boolean shouldExpandContainer = boundsSmallerThanMinDimensions(bounds,
+                minDimensions);
+        if (!overlayContainers.isEmpty()) {
+            for (final TaskFragmentContainer overlayContainer : overlayContainers) {
+                if (!overlayTag.equals(overlayContainer.getOverlayTag())
+                        && taskId == overlayContainer.getTaskId()) {
+                    // If there's an overlay container with different tag shown in the same
+                    // task, dismiss the existing overlay container.
+                    overlayContainer.finish(false /* shouldFinishDependant */, mPresenter,
+                            wct, SplitController.this);
+                }
+                if (overlayTag.equals(overlayContainer.getOverlayTag())
+                        && taskId != overlayContainer.getTaskId()) {
+                    // If there's an overlay container with same tag in a different task,
+                    // dismiss the overlay container since the tag must be unique per process.
+                    overlayContainer.finish(false /* shouldFinishDependant */, mPresenter,
+                            wct, SplitController.this);
+                }
+                if (overlayTag.equals(overlayContainer.getOverlayTag())
+                        && taskId == overlayContainer.getTaskId()) {
+                    // If there's an overlay container with the same tag and task ID, we treat
+                    // the OverlayCreateParams as the update to the container.
+                    final Rect taskBounds = overlayContainer.getTaskContainer().getTaskProperties()
+                            .getTaskMetrics().getBounds();
+                    final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
+                    final Rect sanitizedBounds = sanitizeBounds(bounds, intent, taskBounds);
+                    mPresenter.resizeTaskFragment(wct, overlayToken, sanitizedBounds);
+                    mPresenter.setTaskFragmentIsolatedNavigation(wct, overlayToken,
+                            !sanitizedBounds.isEmpty());
+                    // We can just return the updated overlay container and don't need to
+                    // check other condition since we only have one OverlayCreateParams, and
+                    // if the tag and task are matched, it's impossible to match another task
+                    // or tag since tags and tasks are all unique.
+                    return overlayContainer;
+                }
+            }
+        }
+        return createEmptyContainer(wct, intent, taskId,
+                (shouldExpandContainer ? new Rect() : bounds), launchActivity, overlayTag);
+    }
+
     private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter {
 
         @Override
@@ -2417,8 +2569,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
                 final TaskFragmentContainer launchedInTaskFragment;
                 if (launchingActivity != null) {
                     final int taskId = getTaskId(launchingActivity);
-                    launchedInTaskFragment = resolveStartActivityIntent(wct, taskId, intent,
-                            launchingActivity);
+                    final OverlayCreateParams overlayCreateParams =
+                            OverlayCreateParams.fromBundle(options);
+                    if (Flags.activityEmbeddingOverlayPresentationFlag()
+                            && overlayCreateParams != null) {
+                        launchedInTaskFragment = createOrUpdateOverlayTaskFragmentIfNeeded(wct,
+                                overlayCreateParams, taskId, intent, launchingActivity);
+                    } else {
+                        launchedInTaskFragment = resolveStartActivityIntent(wct, taskId, intent,
+                                launchingActivity);
+                    }
                 } else {
                     launchedInTaskFragment = resolveStartActivityIntentFromNonActivityContext(wct,
                             intent);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index d894487fafb60c005d9c117138a71b6c32fb309a..66e76c59565200a8303074d36fc193303c153476 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -30,12 +30,10 @@ import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.IBinder;
-import android.util.DisplayMetrics;
 import android.util.LayoutDirection;
 import android.util.Pair;
 import android.util.Size;
 import android.view.View;
-import android.view.WindowInsets;
 import android.view.WindowMetrics;
 import android.window.TaskFragmentAnimationParams;
 import android.window.TaskFragmentCreationParams;
@@ -307,8 +305,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
         }
 
         final int taskId = primaryContainer.getTaskId();
-        final TaskFragmentContainer secondaryContainer = mController.newContainer(
-                null /* pendingAppearedActivity */, activityIntent, launchingActivity, taskId,
+        final TaskFragmentContainer secondaryContainer = mController.newContainer(activityIntent,
+                launchingActivity, taskId,
                 // Pass in the primary container to make sure it is added right above the primary.
                 primaryContainer);
         final TaskContainer taskContainer = mController.getTaskContainer(taskId);
@@ -618,7 +616,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
             @NonNull SplitRule rule, @NonNull SplitAttributes defaultSplitAttributes,
             @Nullable Pair<Size, Size> minDimensionsPair) {
         final Configuration taskConfiguration = taskProperties.getConfiguration();
-        final WindowMetrics taskWindowMetrics = getTaskWindowMetrics(taskConfiguration);
+        final WindowMetrics taskWindowMetrics = taskProperties.getTaskMetrics();
         final Function<SplitAttributesCalculatorParams, SplitAttributes> calculator =
                 mController.getSplitAttributesCalculator();
         final boolean areDefaultConstraintsSatisfied = rule.checkParentMetrics(taskWindowMetrics);
@@ -713,11 +711,15 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
         return new Size(windowLayout.minWidth, windowLayout.minHeight);
     }
 
-    private static boolean boundsSmallerThanMinDimensions(@NonNull Rect bounds,
+    static boolean boundsSmallerThanMinDimensions(@NonNull Rect bounds,
             @Nullable Size minDimensions) {
         if (minDimensions == null) {
             return false;
         }
+        // Empty bounds mean the bounds follow the parent host task's bounds. Skip the check.
+        if (bounds.isEmpty()) {
+            return false;
+        }
         return bounds.width() < minDimensions.getWidth()
                 || bounds.height() < minDimensions.getHeight();
     }
@@ -1066,14 +1068,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
 
     @NonNull
     WindowMetrics getTaskWindowMetrics(@NonNull Activity activity) {
-        return getTaskWindowMetrics(getTaskProperties(activity).getConfiguration());
-    }
-
-    @NonNull
-    static WindowMetrics getTaskWindowMetrics(@NonNull Configuration taskConfiguration) {
-        final Rect taskBounds = taskConfiguration.windowConfiguration.getBounds();
-        // TODO(b/190433398): Supply correct insets.
-        final float density = taskConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
-        return new WindowMetrics(taskBounds, WindowInsets.CONSUMED, density);
+        return getTaskProperties(activity).getTaskMetrics();
     }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 9a0769a82d990d34cb252714bea0392e065f8d0e..f4427aaba182891a7c7d95d4715ef930ff6b7243 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -30,7 +30,10 @@ import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.util.ArraySet;
+import android.util.DisplayMetrics;
 import android.util.Log;
+import android.view.WindowInsets;
+import android.view.WindowMetrics;
 import android.window.TaskFragmentInfo;
 import android.window.TaskFragmentParentInfo;
 import android.window.WindowContainerTransaction;
@@ -61,6 +64,10 @@ class TaskContainer {
     @Nullable
     private SplitPinContainer mSplitPinContainer;
 
+    /** The overlay container in this Task. */
+    @Nullable
+    private TaskFragmentContainer mOverlayContainer;
+
     @NonNull
     private final Configuration mConfiguration;
 
@@ -221,6 +228,12 @@ class TaskContainer {
         return null;
     }
 
+    /** Returns the overlay container in the task, or {@code null} if it doesn't exist. */
+    @Nullable
+    TaskFragmentContainer getOverlayContainer() {
+        return mOverlayContainer;
+    }
+
     int indexOf(@NonNull TaskFragmentContainer child) {
         return mContainers.indexOf(child);
     }
@@ -311,8 +324,8 @@ class TaskContainer {
         onTaskFragmentContainerUpdated();
     }
 
-    void removeTaskFragmentContainers(@NonNull List<TaskFragmentContainer> taskFragmentContainer) {
-        mContainers.removeAll(taskFragmentContainer);
+    void removeTaskFragmentContainers(@NonNull List<TaskFragmentContainer> taskFragmentContainers) {
+        mContainers.removeAll(taskFragmentContainers);
         onTaskFragmentContainerUpdated();
     }
 
@@ -332,6 +345,15 @@ class TaskContainer {
     }
 
     private void onTaskFragmentContainerUpdated() {
+        // TODO(b/300211704): Find a better mechanism to handle the z-order in case we introduce
+        //  another special container that should also be on top in the future.
+        updateSplitPinContainerIfNecessary();
+        // Update overlay container after split pin container since the overlay should be on top of
+        // pin container.
+        updateOverlayContainerIfNecessary();
+    }
+
+    private void updateSplitPinContainerIfNecessary() {
         if (mSplitPinContainer == null) {
             return;
         }
@@ -344,10 +366,7 @@ class TaskContainer {
         }
 
         // Ensure the pinned container is top-most.
-        if (pinnedContainerIndex != mContainers.size() - 1) {
-            mContainers.remove(pinnedContainer);
-            mContainers.add(pinnedContainer);
-        }
+        moveContainerToLastIfNecessary(pinnedContainer);
 
         // Update the primary container adjacent to the pinned container if needed.
         final TaskFragmentContainer adjacentContainer =
@@ -359,6 +378,31 @@ class TaskContainer {
         }
     }
 
+    private void updateOverlayContainerIfNecessary() {
+        final List<TaskFragmentContainer> overlayContainers = mContainers.stream()
+                .filter(TaskFragmentContainer::isOverlay).toList();
+        if (overlayContainers.size() > 1) {
+            throw new IllegalStateException("There must be at most one overlay container per Task");
+        }
+        mOverlayContainer = overlayContainers.isEmpty() ? null : overlayContainers.get(0);
+        if (mOverlayContainer != null) {
+            moveContainerToLastIfNecessary(mOverlayContainer);
+        }
+    }
+
+    /** Moves the {@code container} to the last to align taskFragments' z-order. */
+    private void moveContainerToLastIfNecessary(@NonNull TaskFragmentContainer container) {
+        final int index = mContainers.indexOf(container);
+        if (index < 0) {
+            Log.w(TAG, "The container:" + container + " is not in the container list!");
+            return;
+        }
+        if (index != mContainers.size() - 1) {
+            mContainers.remove(container);
+            mContainers.add(container);
+        }
+    }
+
     /**
      * Gets the descriptors of split states in this Task.
      *
@@ -398,6 +442,15 @@ class TaskContainer {
             return mConfiguration;
         }
 
+        /** A helper method to return task {@link WindowMetrics} from this {@link TaskProperties} */
+        @NonNull
+        WindowMetrics getTaskMetrics() {
+            final Rect taskBounds = mConfiguration.windowConfiguration.getBounds();
+            // TODO(b/190433398): Supply correct insets.
+            final float density = mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+            return new WindowMetrics(taskBounds, WindowInsets.CONSUMED, density);
+        }
+
         /** Translates the given absolute bounds to relative bounds in this Task coordinate. */
         void translateAbsoluteBoundsToRelativeBounds(@NonNull Rect inOutBounds) {
             if (inOutBounds.isEmpty()) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 0a694b5c3b645b1e184b0755e1ad8ef61c7683b8..2ba5c9b320f1ea9857c02f5460e8c866a8ae980d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -102,6 +102,9 @@ class TaskFragmentContainer {
      */
     private final List<IBinder> mActivitiesToFinishOnExit = new ArrayList<>();
 
+    @Nullable
+    private final String mOverlayTag;
+
     /** Indicates whether the container was cleaned up after the last activity was removed. */
     private boolean mIsFinished;
 
@@ -157,15 +160,29 @@ class TaskFragmentContainer {
      */
     private boolean mHasCrossProcessActivities;
 
+    /**
+     * @see #TaskFragmentContainer(Activity, Intent, TaskContainer, SplitController,
+     * TaskFragmentContainer, String)
+     */
+    TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
+                          @Nullable Intent pendingAppearedIntent,
+                          @NonNull TaskContainer taskContainer,
+                          @NonNull SplitController controller,
+                          @Nullable TaskFragmentContainer pairedPrimaryContainer) {
+        this(pendingAppearedActivity, pendingAppearedIntent, taskContainer,
+                controller, pairedPrimaryContainer, null /* overlayTag */);
+    }
+
     /**
      * Creates a container with an existing activity that will be re-parented to it in a window
      * container transaction.
      * @param pairedPrimaryContainer    when it is set, the new container will be add right above it
+     * @param overlayTag                Sets to indicate this taskFragment is an overlay container
      */
     TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
             @Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer,
             @NonNull SplitController controller,
-            @Nullable TaskFragmentContainer pairedPrimaryContainer) {
+            @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag) {
         if ((pendingAppearedActivity == null && pendingAppearedIntent == null)
                 || (pendingAppearedActivity != null && pendingAppearedIntent != null)) {
             throw new IllegalArgumentException(
@@ -174,6 +191,8 @@ class TaskFragmentContainer {
         mController = controller;
         mToken = new Binder("TaskFragmentContainer");
         mTaskContainer = taskContainer;
+        mOverlayTag = overlayTag;
+
         if (pairedPrimaryContainer != null) {
             // The TaskFragment will be positioned right above the paired container.
             if (pairedPrimaryContainer.getTaskContainer() != taskContainer) {
@@ -863,6 +882,20 @@ class TaskFragmentContainer {
         return mTaskContainer.indexOf(this) > mTaskContainer.indexOf(other);
     }
 
+    /** Returns whether this taskFragment container is an overlay container. */
+    boolean isOverlay() {
+        return mOverlayTag != null;
+    }
+
+    /**
+     * Returns the tag specified in {@link OverlayCreateParams#getTag()}. {@code null} if this
+     * taskFragment container is not an overlay container.
+     */
+    @Nullable
+    String getOverlayTag() {
+        return mOverlayTag;
+    }
+
     @Override
     public String toString() {
         return toString(true /* includeContainersToFinishOnExit */);
@@ -881,6 +914,7 @@ class TaskFragmentContainer {
                 + " topNonFinishingActivity=" + getTopNonFinishingActivity()
                 + " runningActivityCount=" + getRunningActivityCount()
                 + " isFinished=" + mIsFinished
+                + " overlayTag=" + mOverlayTag
                 + " lastRequestedBounds=" + mLastRequestedBounds
                 + " pendingAppearedActivities=" + mPendingAppearedActivities
                 + (includeContainersToFinishOnExit ? " containersToFinishOnExit="
diff --git a/libs/WindowManager/Jetpack/tests/unittest/Android.bp b/libs/WindowManager/Jetpack/tests/unittest/Android.bp
index ed2ff2de245b6db6c6bcf18877dca3217f7c340f..4ddbd13978d57d39999640d3865b69a26fdb865e 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/Android.bp
+++ b/libs/WindowManager/Jetpack/tests/unittest/Android.bp
@@ -36,6 +36,7 @@ android_test {
         "androidx.test.runner",
         "androidx.test.rules",
         "androidx.test.ext.junit",
+        "flag-junit",
         "mockito-target-extended-minus-junit4",
         "truth",
         "testables",
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..af8017ae9544a0b7785ff8ae86d36575bd025aac
--- /dev/null
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -0,0 +1,393 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
+import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS;
+import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_BOUNDS;
+import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_TAG;
+import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_TASK_ID;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.window.TaskFragmentInfo;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
+import androidx.window.extensions.layout.WindowLayoutComponentImpl;
+import androidx.window.extensions.layout.WindowLayoutInfo;
+
+import com.android.window.flags.Flags;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test class for overlay presentation feature.
+ *
+ * Build/Install/Run:
+ *  atest WMJetpackUnitTests:OverlayPresentationTest
+ */
+// Suppress GuardedBy warning on unit tests
+@SuppressWarnings("GuardedBy")
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class OverlayPresentationTest {
+
+    @Rule
+    public final SetFlagsRule mSetFlagRule = new SetFlagsRule();
+
+    private static final OverlayCreateParams TEST_OVERLAY_CREATE_PARAMS =
+            new OverlayCreateParams(TASK_ID, "test,", new Rect(0, 0, 200, 200));
+
+    private SplitController.ActivityStartMonitor mMonitor;
+
+    private Intent mIntent;
+
+    private TaskFragmentContainer mOverlayContainer1;
+
+    private TaskFragmentContainer mOverlayContainer2;
+
+    private Activity mActivity;
+    @Mock
+    private Resources mActivityResources;
+
+    @Mock
+    private WindowContainerTransaction mTransaction;
+    @Mock
+    private Handler mHandler;
+    @Mock
+    private WindowLayoutComponentImpl mWindowLayoutComponent;
+
+    private SplitController mSplitController;
+    private SplitPresenter mSplitPresenter;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent)
+                .getCurrentWindowLayoutInfo(anyInt(), any());
+        DeviceStateManagerFoldingFeatureProducer producer =
+                mock(DeviceStateManagerFoldingFeatureProducer.class);
+        mSplitController = new SplitController(mWindowLayoutComponent, producer);
+        mSplitPresenter = mSplitController.mPresenter;
+        mMonitor = mSplitController.getActivityStartMonitor();
+        mIntent = new Intent();
+
+        spyOn(mSplitController);
+        spyOn(mSplitPresenter);
+        spyOn(mMonitor);
+
+        doNothing().when(mSplitPresenter).applyTransaction(any(), anyInt(), anyBoolean());
+        final Configuration activityConfig = new Configuration();
+        activityConfig.windowConfiguration.setBounds(TASK_BOUNDS);
+        activityConfig.windowConfiguration.setMaxBounds(TASK_BOUNDS);
+        doReturn(activityConfig).when(mActivityResources).getConfiguration();
+        doReturn(mHandler).when(mSplitController).getHandler();
+        mActivity = createMockActivity();
+
+        mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG);
+    }
+
+    /** Creates a mock activity in the organizer process. */
+    @NonNull
+    private Activity createMockActivity() {
+        final Activity activity = mock(Activity.class);
+        doReturn(mActivityResources).when(activity).getResources();
+        final IBinder activityToken = new Binder();
+        doReturn(activityToken).when(activity).getActivityToken();
+        doReturn(activity).when(mSplitController).getActivity(activityToken);
+        doReturn(TASK_ID).when(activity).getTaskId();
+        doReturn(new ActivityInfo()).when(activity).getActivityInfo();
+        doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
+        return activity;
+    }
+
+    @Test
+    public void testOverlayCreateParamsFromBundle() {
+        assertThat(OverlayCreateParams.fromBundle(new Bundle())).isNull();
+
+        assertThat(OverlayCreateParams.fromBundle(createOverlayCreateParamsTestBundle()))
+                .isEqualTo(TEST_OVERLAY_CREATE_PARAMS);
+    }
+
+    @Test
+    public void testStartActivity_overlayFeatureDisabled_notInvokeCreateOverlayContainer() {
+        mSetFlagRule.disableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG);
+
+        mMonitor.onStartActivity(mActivity, mIntent, createOverlayCreateParamsTestBundle());
+
+        verify(mSplitController, never()).createOrUpdateOverlayTaskFragmentIfNeeded(any(), any(),
+                anyInt(), any(), any());
+    }
+
+    @NonNull
+    private static Bundle createOverlayCreateParamsTestBundle() {
+        final Bundle bundle = new Bundle();
+
+        final Bundle paramsBundle = new Bundle();
+        paramsBundle.putInt(KEY_OVERLAY_CREATE_PARAMS_TASK_ID,
+                TEST_OVERLAY_CREATE_PARAMS.getTaskId());
+        paramsBundle.putString(KEY_OVERLAY_CREATE_PARAMS_TAG, TEST_OVERLAY_CREATE_PARAMS.getTag());
+        paramsBundle.putObject(KEY_OVERLAY_CREATE_PARAMS_BOUNDS,
+                TEST_OVERLAY_CREATE_PARAMS.getBounds());
+
+        bundle.putBundle(KEY_OVERLAY_CREATE_PARAMS, paramsBundle);
+
+        return bundle;
+    }
+
+    @Test
+    public void testGetOverlayContainers() {
+        assertThat(mSplitController.getAllOverlayTaskFragmentContainers()).isEmpty();
+
+        final TaskFragmentContainer overlayContainer1 =
+                createTestOverlayContainer(TASK_ID, "test1");
+
+        assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+                .containsExactly(overlayContainer1);
+
+        assertThrows(
+                "The exception must throw if there are two overlay containers in the same task.",
+                IllegalStateException.class,
+                () -> createTestOverlayContainer(TASK_ID, "test2"));
+
+        final TaskFragmentContainer overlayContainer3 =
+                createTestOverlayContainer(TASK_ID + 1, "test3");
+
+        assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+                .containsExactly(overlayContainer1, overlayContainer3);
+    }
+
+    @Test
+    public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_taskIdNotMatch_throwException() {
+        assertThrows("The method must return null due to task mismatch between"
+                + " launchingActivity and OverlayCreateParams", IllegalArgumentException.class,
+                () -> createOrUpdateOverlayTaskFragmentIfNeeded(
+                        TEST_OVERLAY_CREATE_PARAMS, TASK_ID + 1));
+    }
+
+    @Test
+    public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_anotherTagInTask_dismissOverlay() {
+        createExistingOverlayContainers();
+
+        final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+                new OverlayCreateParams(TASK_ID, "test3", new Rect(0, 0, 100, 100)), TASK_ID);
+
+        assertWithMessage("overlayContainer1 must be dismissed since the new overlay container"
+                + " is launched to the same task")
+                .that(mSplitController.getAllOverlayTaskFragmentContainers())
+                .containsExactly(mOverlayContainer2, overlayContainer);
+    }
+
+    @Test
+    public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_sameTagAnotherTask_dismissOverlay() {
+        createExistingOverlayContainers();
+
+        final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+                new OverlayCreateParams(TASK_ID + 2, "test1", new Rect(0, 0, 100, 100)),
+                TASK_ID + 2);
+
+        assertWithMessage("overlayContainer1 must be dismissed since the new overlay container"
+                + " is launched with the same tag as an existing overlay container in a different "
+                + "task")
+                .that(mSplitController.getAllOverlayTaskFragmentContainers())
+                .containsExactly(mOverlayContainer2, overlayContainer);
+    }
+
+    @Test
+    public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_sameTagAndTask_updateOverlay() {
+        createExistingOverlayContainers();
+
+        final Rect bounds = new Rect(0, 0, 100, 100);
+        final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+                new OverlayCreateParams(TASK_ID, "test1", bounds),
+                TASK_ID);
+
+        assertWithMessage("overlayContainer1 must be updated since the new overlay container"
+                + " is launched with the same tag and task")
+                .that(mSplitController.getAllOverlayTaskFragmentContainers())
+                .containsExactly(mOverlayContainer1, mOverlayContainer2);
+
+        assertThat(overlayContainer).isEqualTo(mOverlayContainer1);
+        verify(mSplitPresenter).resizeTaskFragment(eq(mTransaction),
+                eq(mOverlayContainer1.getTaskFragmentToken()), eq(bounds));
+    }
+
+    @Test
+    public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_dismissMultipleOverlays() {
+        createExistingOverlayContainers();
+
+        final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+                new OverlayCreateParams(TASK_ID, "test2", new Rect(0, 0, 100, 100)),
+                TASK_ID);
+
+        // OverlayContainer1 is dismissed since new container is launched in the same task with
+        // different tag. OverlayContainer2 is dismissed since new container is launched with the
+        // same tag in different task.
+        assertWithMessage("overlayContainer1 and overlayContainer2 must be dismissed")
+                .that(mSplitController.getAllOverlayTaskFragmentContainers())
+                .containsExactly(overlayContainer);
+    }
+
+    private void createExistingOverlayContainers() {
+        mOverlayContainer1 = createTestOverlayContainer(TASK_ID, "test1");
+        mOverlayContainer2 = createTestOverlayContainer(TASK_ID + 1, "test2");
+        List<TaskFragmentContainer> overlayContainers = mSplitController
+                .getAllOverlayTaskFragmentContainers();
+        assertThat(overlayContainers).containsExactly(mOverlayContainer1, mOverlayContainer2);
+    }
+
+    @Test
+    public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_smallerThanMinDimens_expandOverlay() {
+        mIntent.setComponent(new ComponentName(ApplicationProvider.getApplicationContext(),
+                MinimumDimensionActivity.class));
+
+        final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+                TEST_OVERLAY_CREATE_PARAMS, TASK_ID);
+        final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
+
+        assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+                .containsExactly(overlayContainer);
+        assertThat(overlayContainer.areLastRequestedBoundsEqual(new Rect())).isTrue();
+        verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
+                false);
+
+        // Call createOrUpdateOverlayTaskFragmentIfNeeded again to check the update case.
+        clearInvocations(mSplitPresenter);
+        createOrUpdateOverlayTaskFragmentIfNeeded(TEST_OVERLAY_CREATE_PARAMS, TASK_ID);
+
+        verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
+        verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
+                false);
+        assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+                .containsExactly(overlayContainer);
+    }
+
+    @Test
+    public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_notInTaskBounds_expandOverlay() {
+        final Rect bounds = new Rect(TASK_BOUNDS);
+        bounds.offset(10, 10);
+        final OverlayCreateParams paramsOutsideTaskBounds = new OverlayCreateParams(TASK_ID,
+                "test", bounds);
+
+        final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+                paramsOutsideTaskBounds, TASK_ID);
+        final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
+
+        assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+                .containsExactly(overlayContainer);
+        assertThat(overlayContainer.areLastRequestedBoundsEqual(new Rect())).isTrue();
+        verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
+                false);
+
+        // Call createOrUpdateOverlayTaskFragmentIfNeeded again to check the update case.
+        clearInvocations(mSplitPresenter);
+        createOrUpdateOverlayTaskFragmentIfNeeded(paramsOutsideTaskBounds, TASK_ID);
+
+        verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
+        verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
+                false);
+        assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+                .containsExactly(overlayContainer);
+    }
+
+    @Test
+    public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_createOverlay() {
+        final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+                TEST_OVERLAY_CREATE_PARAMS, TASK_ID);
+
+        assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+                .containsExactly(overlayContainer);
+        assertThat(overlayContainer.getTaskId()).isEqualTo(TASK_ID);
+        assertThat(overlayContainer
+                .areLastRequestedBoundsEqual(TEST_OVERLAY_CREATE_PARAMS.getBounds())).isTrue();
+        assertThat(overlayContainer.getOverlayTag()).isEqualTo(TEST_OVERLAY_CREATE_PARAMS.getTag());
+    }
+
+    /**
+     * A simplified version of {@link SplitController.ActivityStartMonitor
+     * #createOrUpdateOverlayTaskFragmentIfNeeded}
+     */
+    @Nullable
+    private TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(
+            @NonNull OverlayCreateParams params, int taskId) {
+        return mSplitController.createOrUpdateOverlayTaskFragmentIfNeeded(mTransaction, params,
+                taskId, mIntent, mActivity);
+    }
+
+    @NonNull
+    private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag) {
+        TaskFragmentContainer overlayContainer = mSplitController.newContainer(
+                null /* pendingAppearedActivity */, mIntent, mActivity, taskId,
+                null /* pairedPrimaryContainer */, tag);
+        setupTaskFragmentInfo(overlayContainer, mActivity);
+        return overlayContainer;
+    }
+
+    private void setupTaskFragmentInfo(@NonNull TaskFragmentContainer container,
+                                       @NonNull Activity activity) {
+        final TaskFragmentInfo info = createMockTaskFragmentInfo(container, activity);
+        container.setInfo(mTransaction, info);
+        mSplitPresenter.mFragmentInfos.put(container.getTaskFragmentToken(), info);
+    }
+}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index d440a3eb95de1243819d040ada89f8d5975562c6..6c0b3cf7971d5362d9c2e6ae2a151e8338dddbb3 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -60,6 +60,7 @@ import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.clearInvocations;
@@ -634,7 +635,8 @@ public class SplitControllerTest {
                 false /* isOnReparent */);
 
         assertFalse(result);
-        verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
+        verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
+                anyString());
     }
 
     @Test
@@ -796,7 +798,8 @@ public class SplitControllerTest {
                 false /* isOnReparent */);
 
         assertTrue(result);
-        verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
+        verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
+                anyString());
         verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
     }
 
@@ -838,7 +841,8 @@ public class SplitControllerTest {
                 false /* isOnReparent */);
 
         assertTrue(result);
-        verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
+        verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
+                anyString());
         verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
     }
 
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 738c94e82a950411aeb544ee6bf1f43d23c1a1c6..79f306ece283c6df92e7d825753cf1ae371f9c1d 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 @@ package com.android.wm.shell.bubbles.animation;
 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 @@ public class ExpandedAnimationController
         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 0000000000000000000000000000000000000000..2a44f04f1358de19552ee6f71730bd2ff519956e
--- /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 aad2683943050ea11b2aa6f83df2b4bd567b864f..e48732801094320b5dd8b229c5b03220fdc0ae97 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 @@ public class StackAnimationController extends
         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 @@ public class StackAnimationController extends
             };
             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 @@ public class StackAnimationController extends
      * 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/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index 7f020725d61f173f96cc8708011997f65aada280..acfb259d73593cfe8598f76ff93288da9808d67b 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -39,7 +39,25 @@ filegroup {
 }
 
 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 @@ android_test {
     ],
     exclude_srcs: [
         ":WMShellFlickerTestsBubbles-src",
-        ":WMShellFlickerTestsPip-src",
+        ":WMShellFlickerTestsPip1-src",
+        ":WMShellFlickerTestsPip2-src",
+        ":WMShellFlickerTestsPip3-src",
         ":WMShellFlickerTestsPipCommon-src",
         ":WMShellFlickerTestsPipApps-src",
         ":WMShellFlickerTestsSplitScreenGroup1-src",
@@ -200,18 +220,48 @@ android_test {
 }
 
 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",
+        ":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",
-        ":WMShellFlickerTestsPip-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"],
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 19c8435facaf8fdcc667c48b30e52bf27befa287..94e3959782ed1c1a0b50ae1ad6d939c1a0ef8a7e 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.FlickerBuilder
 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 @@ import org.junit.runners.Parameterized
 @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/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 7f226939ffaaaa21e7c4dfd9f47b7ba293866ac9..d056248273b2ac3bf58f2aacd4bfce50e5e3f2fa 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -785,7 +785,7 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntry(
       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 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal(
 
       // 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 b00fc699e515d2e212c941e3e8afe53760e8ce7e..14602ef926d3495468f66817e0df0630a45e2ae3 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -139,6 +139,7 @@ CanvasContext::~CanvasContext() {
     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 1c3399a6650d743f9a962c4a55cb8a18808c7bf7..2362331aca26337898a2c99c45ecace90a575aa1 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.cpp
+++ b/libs/hwui/renderthread/HintSessionWrapper.cpp
@@ -158,7 +158,6 @@ void HintSessionWrapper::sendLoadResetHint() {
 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 a14ae1cc46ecde3e2ed771148b1ccc053356cbd8..10a740a1f803916628ed30aefe9e07c1e29ce6bb 100644
--- a/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
+++ b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
@@ -259,6 +259,31 @@ TEST_F(HintSessionWrapperTests, delayedDeletionResolvesAfterAsyncCreationFinishe
 
 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 @@ TEST_F(HintSessionWrapperTests, delayedDeletionDoesNotKillReusedSession) {
     // 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 f69dd3cc3206ee9bbc2f91abcd45ea0d7ad80a6b..349340804f1e64d72b2669c3677bb7ca783ed90b 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -23,6 +23,10 @@ aidl_interface {
     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_interface {
         "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/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 91fa873078fcaebe57b19b5ddcb66000af115315..cccf6f1caca7ff9568bf7f8b058b37e03ebf6926 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -18,6 +18,9 @@ package android.media;
 
 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 @@ public final class MediaRoute2Info implements Parcelable {
                 TYPE_WIRED_HEADPHONES,
                 TYPE_BLUETOOTH_A2DP,
                 TYPE_HDMI,
+                TYPE_HDMI_ARC,
+                TYPE_HDMI_EARC,
                 TYPE_USB_DEVICE,
                 TYPE_USB_ACCESSORY,
                 TYPE_DOCK,
@@ -205,6 +210,22 @@ public final class MediaRoute2Info implements Parcelable {
      */
     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.
      *
@@ -907,6 +928,10 @@ public final class MediaRoute2Info implements Parcelable {
                 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 159427bc27966edac5ca87b03e613ca918afb81f..76a00acfa1c4c3cca1297098f4214345d7c95686 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -310,8 +310,11 @@ public final class MediaRouter2 {
                 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,13 +2060,7 @@ public final class MediaRouter2 {
 
         @Override
         public RoutingSessionInfo getSystemSessionInfo() {
-            RoutingSessionInfo result;
-            try {
-                result = mMediaRouterService.getSystemSessionInfoForPackage(mClientPackageName);
-            } catch (RemoteException ex) {
-                throw ex.rethrowFromSystemServer();
-            }
-            return ensureClientPackageNameForSystemSession(result);
+            return getSystemSessionInfoImpl(mMediaRouterService, mClientPackageName);
         }
 
         /**
@@ -2427,6 +2424,23 @@ public final class MediaRouter2 {
             releaseSession(controller.getRoutingSessionInfo());
         }
 
+        /**
+         * 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/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl
index 31e65eb13926a74f09d23e865e41f6fe54eb3b25..d0e860c9109f181f2b6186506f55e54089d05a41 100644
--- a/media/java/android/media/projection/IMediaProjectionManager.aidl
+++ b/media/java/android/media/projection/IMediaProjectionManager.aidl
@@ -176,4 +176,21 @@ interface IMediaProjectionManager {
     @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/src/com/android/settingslib/media/DeviceIconUtil.java b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
index e38e041a87dc1077eae456cc1a32e04fab1e9c79..2a2841745fa06d4cd01cd66b2dae1ebc0c028bc0 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 @@ public class DeviceIconUtil {
     @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 bf63f5d80450a84b72a777700924da7d65fd15cd..5dacba5357cde263b398d3248486104a5c2b2f4f 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_BUILTIN_SPEAKER;
 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 @@ public abstract class InfoMediaManager extends MediaManager {
             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 @@ public abstract class InfoMediaManager extends MediaManager {
                                 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 147412d08d2ce2620c5c844c3c82250490fd11f5..8085c998abea71ab719ed1551be60e1a3940e7fd 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_BUILTIN_SPEAKER;
 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 @@ public abstract class MediaDevice implements Comparable<MediaDevice> {
             mType = MediaDeviceType.TYPE_BLUETOOTH_DEVICE;
             return;
         }
-
         switch (info.getType()) {
             case TYPE_GROUP:
                 mType = MediaDeviceType.TYPE_CAST_GROUP_DEVICE;
@@ -157,6 +158,8 @@ public abstract class MediaDevice implements Comparable<MediaDevice> {
             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 a63bbdf36fa86550ca7dcb08726999a77bd0ef96..c44f66e99d00148660755b65d421cabeb8c05e08 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 @@ package com.android.settingslib.media;
 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 @@ public class PhoneMediaDevice extends MediaDevice {
                 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 @@ public class PhoneMediaDevice extends MediaDevice {
             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/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 03f7c9968a1df98230759c3843f1426eace4b902..de73b77b21a30eb16489247ace9203bab9e1c43a 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -31,22 +31,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",
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
index 0f55f35adc4e23fb61ba94ad4251745129d27f7d..eadcd7c27a18c5e57972b89afafca0c87fa0ed87 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 8841967b153510132d85452d9f074936a6bd1f89..bcf1535b94fa389f0fef6c4745c3c08c49539734 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 211af908a877b391c9ec869ec5b8184f77b50b76..05675289db642b372cf30fe76bae02315457a622 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -30,6 +30,13 @@ flag {
     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"
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 6c4b695ed7094b0e8d1948570f2ce5a2ae447884..af35ea44322fda3e754744d8fc0e20efce6a8db6 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 @@ constructor(
     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 @@ constructor(
             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 @@ constructor(
         // 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 4ed78b3b95ed930e13ca91ae1c68e60d9b305211..33024f76471056f984b3b607af1c5279ca431d0d 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 @@ fun CommunalGridLayout(
                         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 ac8aa67fa4bf7da8bc03733e9e69df16ecd125f7..4b2a156c1dbd692e5be90d8c109a56b62e4ea55f 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 @@ abstract class CommunalGridLayoutCard {
      *
      * 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 fdf65f5d5cc7a71956cbfb7c54633702a8e5fef7..c1974caa5628d4faf1aa8bf36f010763ab3d1290 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 @@ class CommunalLayoutEngineTest {
             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 c6e429a79e8642321dd9e125a2cd450d4e91a3d2..ddd1c67bd5faaa4c255abe31de47ca905999b4ec 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 @@ object ComposeFacade : BaseComposeFacade {
         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 1722685f42877d217de8d48dd1a6cadceda70c3f..eeda6c63b68fd3ba04638675fb22db37951f9304 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.compose.theme.PlatformTheme
 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 @@ object ComposeFacade : BaseComposeFacade {
         }
     }
 
+    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 0000000000000000000000000000000000000000..46d418a03c00a9defcae0ffb612bf690abe6d042
--- /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 3d827fb5c9a66c4ebe8fa89deb07d5ba3b22ca29..b8fb264068012822a03a02ada40f4e5f73fe9014 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.Modifier
 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 @@ fun CommunalHub(
     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 @@ fun CommunalHub(
                     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 @@ private fun tutorialCard(size: CommunalGridLayoutCard.Size): CommunalGridLayoutC
         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/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index 5a70b79cb1765f6e5471755a9839b2784b333eb5..452bc317e2d5acb1f3d0df2da25994462ef737b4 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 acee4258b64d8405c25aca0d7c5878f0b77d569d..7e03bd9d432597399d827200d31174ed3ddd4624 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 587caaf3ecf3e301c0c81c03709a6b642c756dba..db526b187d38a0bf35c336c33b991751d23d921a 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/strings.xml b/packages/SystemUI/res/values/strings.xml
index 7a6d29ab3c1f58cad67f2347eb8926c928a57f20..9e2ebf60fe8f918b7f671d65926a173cc3c40e09 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 db46ccf6a827fdef17ef602289352ef4bf698abf..80f70a0cd2f2eaaf12466a33089be83d7949136d 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 @@ data class BiometricModalities(
     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/unfold/system/DeviceStateRepository.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateRepository.kt
new file mode 100644
index 0000000000000000000000000000000000000000..f219cece3ff8e8f434c7e6b9d627472e3523e95f
--- /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 fe607e16661cb9396731b9b5630d7e94008bb7bc..7b67e3f3c92099db0e8db3e34d3312313a5f47aa 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
@@ -47,6 +47,9 @@ abstract class SystemUnfoldSharedModule {
     @Binds
     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 0000000000000000000000000000000000000000..63ea1165ee04de7818cd6a5e45c958776da6fd50
--- /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/systemui/GuestResetOrExitSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java
index fd84543ee50bec87c56b6560e529992bd86c51a4..494efb7d3f87faeda737fe81d2fad7caefe950d9 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.DialogInterface;
 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 final class GuestResetOrExitSessionReceiver extends BroadcastReceiver {
     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 @@ public final class GuestResetOrExitSessionReceiver extends BroadcastReceiver {
             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 @@ public final class GuestResetOrExitSessionReceiver extends BroadcastReceiver {
     }
 
     /**
+     * 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 @@ public final class GuestResetOrExitSessionReceiver extends BroadcastReceiver {
                 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 @@ public final class GuestResetOrExitSessionReceiver extends BroadcastReceiver {
      * 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 @@ public final class GuestResetOrExitSessionReceiver extends BroadcastReceiver {
                     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 @@ public final class GuestResetOrExitSessionReceiver extends BroadcastReceiver {
                     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 b573fadea15e89240385e001cc5e2fa002f401d9..0f5f869cba5d741af0495252c4133eecf971f2e8 100644
--- a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
@@ -27,6 +27,7 @@ import androidx.annotation.NonNull;
 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.phone.SystemUIDialog;
 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 @@ public class GuestResumeSessionReceiver {
     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 @@ public class GuestResumeSessionReceiver {
             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/biometrics/AuthBiometricFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
deleted file mode 100644
index 3f2da5e144c57cdab04daf2086cc621534789aa1..0000000000000000000000000000000000000000
--- 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 09eabf2aa430e0fcc61ef3f86191646a3e535480..0000000000000000000000000000000000000000
--- 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 0ad3848299f939ac4b031c00001958d3a4dd23a3..0000000000000000000000000000000000000000
--- 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 054bd082edb7067ee17bce4aa4227429bab5fe3d..8d1d90588fc3f596757605f5f2917411eafcaf4f 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 @@ constructor(
             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 958213afacdf209757d37d5f9bdf004f63c71d86..0000000000000000000000000000000000000000
--- 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/data/repository/DisplayStateRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
index c4c52e8b358ec93826e4e4004988c2ef25885c5f..050b399fd3e80d5059fc0928040b495e612f9c2f 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 @@ import kotlinx.coroutines.flow.stateIn
 /** 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 a317a068405514844fa0162e891c627e4b2d484a..427361d4b17a631e0146fde3f2a777dfa26906bc 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 @@ interface DisplayStateInteractor {
     /** 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 @@ constructor(
     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/ui/BiometricPromptLayout.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
index cef0be09d3ce1e377273fbdef23c1d21ce2c88b5..0d72b9c07d7a53f3b30894d29d85afb5ffd6f1de 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.FrameLayout;
 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 @@ public class BiometricPromptLayout extends LinearLayout {
     }
 
     @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 c29efc0fcab96efc82b95bb3f596985afff0e510..ac48b6a2b11e6d9151410c52b7c652b5dd6eacd6 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.LifecycleOwner
 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.biometrics.ui.viewmodel.PromptViewModel
 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 @@ object BiometricViewBinder {
             !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 @@ object BiometricViewBinder {
 
         // 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 @@ object BiometricViewBinder {
                 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 @@ object BiometricViewBinder {
                     ) {
                         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 @@ object BiometricViewBinder {
                     viewModel.isIconConfirmButton
                         .map { isPending ->
                             when {
-                                isPending && iconController.actsAsConfirmButton ->
+                                isPending && modalities.hasFaceAndFingerprint ->
                                     View.OnTouchListener { _: View, event: MotionEvent ->
                                         viewModel.onOverlayTouch(event)
                                     }
@@ -320,22 +307,11 @@ object BiometricViewBinder {
                             }
                         }
                         .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 @@ object BiometricViewBinder {
 
                             // 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 @@ object BiometricViewBinder {
                 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 @@ class Spaghetti(
     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 @@ class Spaghetti(
 
     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 @@ private fun BiometricModalities.asDefaultHelpMessage(context: Context): String =
         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 d28f1dc78c1333d6cdb6957c27121f2f19a990e6..0000000000000000000000000000000000000000
--- 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 0000000000000000000000000000000000000000..475ef18e50993e2b048ae0355b5422b6d5a03b98
--- /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 dfd3a9b8aebe9257a32af14af9630301b9769b78..0000000000000000000000000000000000000000
--- 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 0000000000000000000000000000000000000000..11a5d8b578df9b27a9ccbf6f9b24a17d3af81ad6
--- /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 267afae118e6ff499400d27e7bb474f509c2f30b..e49b4a7bbce9de7bfa61269305f5ea27ef7ad084 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.BiometricModalities
 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.coroutineScope
 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 @@ import kotlinx.coroutines.launch
 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 @@ constructor(
     /** 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 @@ constructor(
     /** 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 @@ constructor(
             !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 @@ constructor(
     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 @@ constructor(
         _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 @@ constructor(
             if (authenticateAfterError) {
                 showAuthenticating(messageAfterError)
             } else {
-                showInfo(messageAfterError)
+                showHelp(messageAfterError)
             }
         }
     }
@@ -325,15 +368,12 @@ constructor(
     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 @@ constructor(
         _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 @@ constructor(
         _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 @@ constructor(
         _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 @@ constructor(
         _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 @@ constructor(
 
         _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 2bd62587834da6d0ef61c0585090aa51970c5d81..21578f491de7e5c2d9223a21f6917b85a58358b0 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
@@ -146,6 +146,16 @@ constructor(
     /** 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)
diff --git a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
index 0bf50693344aa3a5b079351c9b109f8e0bf0ab85..7d73896bb37069dd1c1e1838ea86cee6b2108865 100644
--- a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
+++ b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
@@ -19,7 +19,6 @@ package com.android.systemui.colorextraction;
 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;
@@ -47,9 +46,7 @@ public class SysuiColorExtractor extends ColorExtractor implements Dumpable,
         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
@@ -82,9 +79,6 @@ public class SysuiColorExtractor extends ColorExtractor implements Dumpable,
         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.
@@ -123,14 +117,6 @@ public class SysuiColorExtractor extends ColorExtractor implements Dumpable,
         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.
      *
@@ -140,14 +126,7 @@ public class SysuiColorExtractor extends ColorExtractor implements Dumpable,
      * - 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
@@ -164,7 +143,5 @@ public class SysuiColorExtractor extends ColorExtractor implements Dumpable,
         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 f9c4f29afee9e28b3c2723dd3e05519fb6c96d01..1a214ba5a3d97a39b1d00af44e26999dbcabd261 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/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
index 53b6879db3d7fb3947654492a05299d59775f814..485e5122cfad98c9c302d223ad35fc116954271d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
@@ -1,5 +1,6 @@
 package com.android.systemui.communal.data.repository
 
+import com.android.systemui.FeatureFlags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
@@ -15,10 +16,11 @@ interface CommunalRepository {
 class CommunalRepositoryImpl
 @Inject
 constructor(
-    private val featureFlags: FeatureFlagsClassic,
+    private val featureFlags: FeatureFlags,
+    private val featureFlagsClassic: FeatureFlagsClassic,
 ) : CommunalRepository {
     override val isCommunalEnabled: Boolean
         get() =
-            featureFlags.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) &&
-                featureFlags.isEnabled(Flags.COMMUNAL_HUB)
+            featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) &&
+                featureFlags.communalHub()
 }
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 f13b62fbfeb90a8dc5a5a5d3cf0b91c4dc563ddf..77025dc8839a12a20009139b0daa8388b937f8af 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.AppWidgetHost
 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 android.os.UserManager
 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 javax.inject.Inject
 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 @@ interface CommunalWidgetRepository {
 
     /** 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 @@ constructor(
     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 @@ constructor(
     // 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)
-        }
+    private val isUserUnlocked: Flow<Boolean> =
+        callbackFlow {
+                if (!communalRepository.isCommunalEnabled) {
+                    awaitClose()
+                }
 
-        if (isUserUnlockingOrUnlocked()) {
-            send()
-            awaitClose()
-        } else {
-            val receiver =
-                object : BroadcastReceiver() {
-                    override fun onReceive(context: Context?, intent: Intent?) {
-                        send()
-                    }
+                fun isUserUnlockingOrUnlocked(): Boolean {
+                    return userManager.isUserUnlockingOrUnlocked(userTracker.userHandle)
                 }
 
-            broadcastDispatcher.registerReceiver(
-                receiver,
-                IntentFilter(Intent.ACTION_USER_UNLOCKED),
-            )
+                fun send() {
+                    trySendWithFailureLogging(isUserUnlockingOrUnlocked(), TAG)
+                }
 
-            awaitClose { broadcastDispatcher.unregisterReceiver(receiver) }
+                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 @@ constructor(
             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 @@ constructor(
             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 04bb6ae75e60a5a40def0880b610ed81e8806ee6..62387079ab353c6da98f92e5141da5e23728ec88 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 @@ package com.android.systemui.communal.domain.interactor
 
 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 @@ constructor(
 
     /** 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 0bd7d86c972d28c125108d4f577878cd6a03179d..0000000000000000000000000000000000000000
--- 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 0803a01b93b8a8e363c87561312dfaa2a101370d..109ed2da7e48089659142c924c20296905f7b4bc 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/model/CommunalContentCategory.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentCategory.kt
new file mode 100644
index 0000000000000000000000000000000000000000..7f05b9cd4943c8b058ff87ef6215b7330f42ef2d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentCategory.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.communal.shared.model
+
+enum class CommunalContentCategory {
+    /** The content persists in the communal hub until removed by the user. */
+    PERSISTENT,
+
+    /** 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/model/CommunalContentSize.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
new file mode 100644
index 0000000000000000000000000000000000000000..39a6476929ed4c7ea19b74a734385f76dc18e84d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
@@ -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 com.android.systemui.communal.shared.model
+
+/** Supported sizes for communal content in the layout grid. */
+enum class CommunalContentSize {
+    /** Content takes the full height of the column. */
+    FULL,
+
+    /** 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/model/CommunalWidgetContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e141dc40477cb2ea084a3b8679e7a476ab36974a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.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.communal.shared.model
+
+import android.appwidget.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 2a08d7f6bc28325463a7a931c63763de10c3d915..0daf7b5610e8de08c891bdc859200ed90bcc0b0c 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.AppWidgetHost
 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 0000000000000000000000000000000000000000..98060dc1dcebcfab438fd67fc11834b190bcd005
--- /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 ddeb1d67b945414fafc8a64edbbf0e33b9a3975f..25c64eafe2557fe0cf8749ff29cac19f9e91bab7 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 8fba342c49bedfa00534ff3e27074a01adcd2d0e..d7bbea64332e8c840445ba0771a19e6a327f7530 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 9221832b4ba1e5e2f3c67af3b659559f6599e382..4bdea75d9d71a8f089741275022b1b9d1925aa18 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 @@ interface BaseComposeFacade {
         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 5d6949b3e87f8232da39c105a994e251d43d8d01..d8ff535ffd782e1087526d336d93570171895736 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.ImmersiveModeConfirmation
 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
@@ -342,11 +341,6 @@ abstract class SystemUICoreStartableModule {
     @ClassKey(KeyguardViewConfigurator::class)
     abstract fun bindKeyguardViewConfigurator(impl: KeyguardViewConfigurator): CoreStartable
 
-    @Binds
-    @IntoMap
-    @ClassKey(LockscreenWallpaper::class)
-    abstract fun bindLockscreenWallpaper(impl: LockscreenWallpaper): CoreStartable
-
     @Binds
     @IntoMap
     @ClassKey(ScrimController::class)
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 d19efbdd80269d579cdac2a43a0e576fb4bf4350..3f215333382eae12b6245b951336897a51cdef59 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.view.View
 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 @@ class MirroringConfirmationDialog(
     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 86ef439361b06af4d4abe330568d68c78c198458..91f535df586a22a40c8a826394c205d8e97d644d 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.dagger.qualifiers.Background
 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 @@ constructor(
     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 @@ constructor(
                     onCancelMirroring = {
                         scope.launch(bgDispatcher) { pendingDisplay.ignore() }
                         hideDialog()
-                    }
+                    },
+                    configurationController
                 )
                 .apply { show() }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index ecd8b0e7fd4ecf068a0ac296c221c382f5d49905..10fac4d328e02649cdd20e6fa9110f19f92c691b 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -549,12 +549,6 @@ object Flags {
     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
@@ -776,7 +770,7 @@ object Flags {
     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")
diff --git a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt
index 7ccc26c063d3da3e830012aa41d77a4aeae95605..4a5cc641dcf1f329a215010167649f6b1109391d 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.flags
 
-import android.util.Log
 import com.android.systemui.Dependency
 
 /**
@@ -65,8 +64,7 @@ private constructor(
      * }
      * ````
      */
-    fun assertInLegacyMode() =
-        check(!isEnabled) { "Legacy code path not supported when $flagName is enabled." }
+    fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, flagName)
 
     /**
      * Called to ensure code is only run when the flag is enabled. This protects users from the
@@ -81,13 +79,8 @@ private constructor(
      * }
      * ```
      */
-    fun isUnexpectedlyInLegacyMode(): Boolean {
-        if (!isEnabled) {
-            val message = "New code path expects $flagName to be enabled."
-            Log.wtf(TAG, message, Exception(message))
-        }
-        return !isEnabled
-    }
+    fun isUnexpectedlyInLegacyMode(): Boolean =
+        RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, flagName)
 
     companion object {
         private const val TAG = "RefactorFlag"
diff --git a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt
new file mode 100644
index 0000000000000000000000000000000000000000..2aa397f3e74467431ce96ca26f64d0286f4cc647
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.flags
+
+import android.util.Log
+
+/**
+ * Utilities for writing your own objects to uphold refactor flag conventions.
+ *
+ * Example usage:
+ * ```
+ * object SomeRefactor {
+ *     const val FLAG_NAME = Flags.SOME_REFACTOR
+ *     @JvmStatic inline val isEnabled get() = Flags.someRefactor()
+ *     @JvmStatic inline fun isUnexpectedlyInLegacyMode() =
+ *         RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+ *     @JvmStatic inline fun assertInLegacyMode() =
+ *         RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+ * }
+ * ```
+ */
+@Suppress("NOTHING_TO_INLINE")
+object RefactorFlagUtils {
+    /**
+     * Called to ensure code is only run when the flag is enabled. This protects users from the
+     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+     * build to ensure that the refactor author catches issues in testing.
+     *
+     * Example usage:
+     * ```
+     * public void setNewController(SomeController someController) {
+     *     if (SomeRefactor.isUnexpectedlyInLegacyMode()) return;
+     *     mSomeController = someController;
+     * }
+     * ```
+     */
+    inline fun isUnexpectedlyInLegacyMode(isEnabled: Boolean, flagName: Any): Boolean {
+        val inLegacyMode = !isEnabled
+        if (inLegacyMode) {
+            val message = "New code path expects $flagName to be enabled."
+            Log.wtf("RefactorFlag", message, IllegalStateException(message))
+        }
+        return inLegacyMode
+    }
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is enabled to ensure that the refactor author catches issues in testing.
+     *
+     * Example usage:
+     * ```
+     * public void setSomeLegacyController(SomeController someController) {
+     *     SomeRefactor.assertInLegacyMode();
+     *     mSomeController = someController;
+     * }
+     * ````
+     */
+    inline fun assertInLegacyMode(isEnabled: Boolean, flagName: Any) =
+        check(!isEnabled) { "Legacy code path not supported when $flagName is enabled." }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 47798955b43da31084c03cffe92567a65bcffa12..2b1cdc2ff026193430bff295617813916ef1a841 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -202,7 +202,7 @@ public class KeyguardService extends Service {
     // 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")
@@ -236,9 +236,8 @@ public class KeyguardService extends Service {
                     }
                 }
                 initAlphaForAnimationTargets(t, apps);
-                if (lockscreenLiveWallpaperEnabled) {
-                    initAlphaForAnimationTargets(t, wallpapers);
-                }
+                initAlphaForAnimationTargets(t, wallpapers);
+
                 t.apply();
 
                 runner.onAnimationStart(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 0bac40bcbcc13bbe28ddda92a5aef7ecd6bc13ff..c8c06ae65d45af48b7195abd7ca2991dc1ea9e91 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 @@ class KeyguardUnlockAnimationController @Inject constructor(
                 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 e893c6305deee74b02568ca993a1f7d82c157354..4e6a872cb3f7035c2fdb5a7e29ca74949b24981b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -184,8 +184,6 @@ import java.util.Objects;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
-
-
 import kotlinx.coroutines.CoroutineDispatcher;
 
 /**
@@ -1517,12 +1515,11 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
             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();
 
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 d06f31fed8dbdb8d479e8082d9b550e521cbab3c..7e360cfad66d2bc102f44c7fb22b195d7415aa9c 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.toQuad
 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 @@ constructor(
     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/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index fbe26de4e9ba324920a80101633e77a192563f7b..b0b857729c77baf929aef5fa3f384e4f6e97defa 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 @@ constructor(
     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/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..023d16cab013c3dff9455abd566e68ed4b0afb59
--- /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 0000000000000000000000000000000000000000..da74f2fa061e514655ffb02f50d3dde1321cb560
--- /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 078318196883ad3e6a852078d4f5b92ae054f73f..0e95be20d059d8cfd39be471ed94665a34958719 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.flags.Flags
 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 kotlin.time.Duration.Companion.milliseconds
 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 @@ constructor(
     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 @@ constructor(
         )
     }
 
-    /** 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/mediaprojection/MediaProjectionMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
index ce8b79cd9e38882adc0a494a5e1e66cd66313cf1..d8cab6ab2a6cf3936c7fd03c0c4bc50e2b1d5e82 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
@@ -23,7 +23,6 @@ import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGE
 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
@@ -36,16 +35,22 @@ import javax.inject.Inject
 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() {
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 2d830d3115625152d1cf832c2f17172e7ba3bd92..922f7e0aabaf7c38180db39b09da4693775ddc0f 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 @@ public class MediaProjectionPermissionActivity extends Activity
             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 @@ public class MediaProjectionPermissionActivity extends Activity
 
         if (savedInstanceState == null) {
             mMediaProjectionMetricsLogger.notifyProjectionInitiated(
+                    mUid,
                     appName == null
                             ? SessionCreationSource.CAST
                             : SessionCreationSource.APP);
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 1b0d5f9a9fdf39bd81a5f1a963b4c11b46029b5b..2f8fe42672c21899ae1f12391370573a3245dafe 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.DrawableRes;
 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.QSHost;
 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 @@ public abstract class SensorPrivacyToggleTile extends QSTileImpl<QSTile.BooleanS
     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 @@ public abstract class SensorPrivacyToggleTile extends QSTileImpl<QSTile.BooleanS
                 statusBarStateController, activityStarter, qsLogger);
         mSensorPrivacyController = sensorPrivacyController;
         mKeyguard = keyguardStateController;
-        mSafetyCenterManager = safetyCenterManager;
+        mIsSafetyCenterEnabled = safetyCenterManager.isSafetyCenterEnabled();
         mSensorPrivacyController.observe(getLifecycle(), this);
     }
 
@@ -138,7 +138,7 @@ public abstract class SensorPrivacyToggleTile extends QSTileImpl<QSTile.BooleanS
 
     @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/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index f469c6b78e871460132f26c45a9a820772347748..f602da47e585bb8055e383854753c9a74b5f3179 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 @@ public class RecordingController
         }
 
         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/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index 1ecb127f0c9268036aeef40036918bf3b1f31ba1..ead10d6da860f042840ae7f58b13171283a7a29d 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 @@ public class BrightnessDialog extends Activity {
     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 @@ public class BrightnessDialog extends Activity {
 
         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/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 09514404d4dba63c5d3a5263ca27f63ea93db871..3d3447b921fc37a5c2504ad9f368b71336adf9b5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -42,10 +42,13 @@ 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.data.repository.CommunalRepository;
+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;
@@ -103,8 +106,10 @@ public class NotificationShadeWindowViewController implements Dumpable {
     private final PulsingGestureListener mPulsingGestureListener;
     private final LockscreenHostedDreamGestureListener mLockscreenHostedDreamGestureListener;
     private final NotificationInsetsController mNotificationInsetsController;
+    private final CommunalViewModel mCommunalViewModel;
+    private final CommunalRepository mCommunalRepository;
     private final boolean mIsTrackpadCommonEnabled;
-    private final FeatureFlags mFeatureFlags;
+    private final FeatureFlagsClassic mFeatureFlagsClassic;
     private final SysUIKeyEventHandler mSysUIKeyEventHandler;
     private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
     private final AlternateBouncerInteractor mAlternateBouncerInteractor;
@@ -175,8 +180,10 @@ public class NotificationShadeWindowViewController implements Dumpable {
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
             PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
+            CommunalViewModel communalViewModel,
+            CommunalRepository communalRepository,
             NotificationExpansionRepository notificationExpansionRepository,
-            FeatureFlags featureFlags,
+            FeatureFlagsClassic featureFlagsClassic,
             SystemClock clock,
             BouncerMessageInteractor bouncerMessageInteractor,
             BouncerLogger bouncerLogger,
@@ -206,8 +213,10 @@ public class NotificationShadeWindowViewController implements Dumpable {
         mPulsingGestureListener = pulsingGestureListener;
         mLockscreenHostedDreamGestureListener = lockscreenHostedDreamGestureListener;
         mNotificationInsetsController = notificationInsetsController;
-        mIsTrackpadCommonEnabled = featureFlags.isEnabled(TRACKPAD_GESTURE_COMMON);
-        mFeatureFlags = featureFlags;
+        mCommunalViewModel = communalViewModel;
+        mCommunalRepository = communalRepository;
+        mIsTrackpadCommonEnabled = featureFlagsClassic.isEnabled(TRACKPAD_GESTURE_COMMON);
+        mFeatureFlagsClassic = featureFlagsClassic;
         mSysUIKeyEventHandler = sysUIKeyEventHandler;
         mPrimaryBouncerInteractor = primaryBouncerInteractor;
         mAlternateBouncerInteractor = alternateBouncerInteractor;
@@ -223,7 +232,7 @@ public class NotificationShadeWindowViewController implements Dumpable {
                 messageAreaControllerFactory,
                 bouncerMessageInteractor,
                 bouncerLogger,
-                featureFlags,
+                featureFlagsClassic,
                 selectedUserInteractor);
 
         collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(),
@@ -234,7 +243,7 @@ public class NotificationShadeWindowViewController implements Dumpable {
                 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));
@@ -263,7 +272,7 @@ public class NotificationShadeWindowViewController implements Dumpable {
         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);
         }
@@ -435,7 +444,7 @@ public class NotificationShadeWindowViewController implements Dumpable {
                 }
 
                 boolean bouncerShowing;
-                if (mFeatureFlags.isEnabled(Flags.ALTERNATE_BOUNCER_VIEW)) {
+                if (mFeatureFlagsClassic.isEnabled(Flags.ALTERNATE_BOUNCER_VIEW)) {
                     bouncerShowing = mPrimaryBouncerInteractor.isBouncerShowing()
                             || mAlternateBouncerInteractor.isVisibleState();
                 } else {
@@ -447,7 +456,7 @@ public class NotificationShadeWindowViewController implements Dumpable {
                     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) {
@@ -479,7 +488,7 @@ public class NotificationShadeWindowViewController implements Dumpable {
                 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();
@@ -494,7 +503,7 @@ public class NotificationShadeWindowViewController implements Dumpable {
                 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;
@@ -559,8 +568,28 @@ public class NotificationShadeWindowViewController implements Dumpable {
         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 (!mCommunalRepository.isCommunalEnabled()
+                || !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/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index a8a20cc8559b9e19183cef11ab871c8260f06230..2f684762a13a8144af2b3f88af4d0ee4f1d98670 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
@@ -35,7 +35,6 @@ 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.UserSwitcherInteractor
-import com.android.systemui.util.kotlin.pairwise
 import javax.inject.Inject
 import javax.inject.Provider
 import kotlinx.coroutines.CoroutineScope
@@ -140,13 +139,6 @@ constructor(
     /** 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
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 f1eb9febfb33459c7f8ed6604d4d2261d90c1252..0000000000000000000000000000000000000000
--- 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 f32f1c2dcd2510baec9cd6396520ab68c550957c..710e59d91c6b92018a34ef66ca90f9431fb8b555 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.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_
 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.ArrayList;
 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 @@ public class NotificationLockscreenUserManagerImpl implements
         @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);
-                        }
-                    }
-                    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);
-                        });
-                    }
-                    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 (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);
                     }
-                    if (notificationKey != null) {
-                        final NotificationVisibility nv = mVisibilityProviderLazy.get()
-                                .obtain(notificationKey, true);
-                        mClickNotifier.onNotificationClick(notificationKey, nv);
+                }
+                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;
+                }
+                if (notificationKey != null) {
+                    final NotificationVisibility nv = mVisibilityProviderLazy.get()
+                            .obtain(notificationKey, true);
+                    mClickNotifier.onNotificationClick(notificationKey, nv);
+                }
             }
         }
     };
@@ -403,6 +398,10 @@ public class NotificationLockscreenUserManagerImpl implements
         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 @@ public class NotificationLockscreenUserManagerImpl implements
         }
     }
 
+    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 389486f0ada37c07ad3fb276b1ef92404a775685..9c4625e91110d2bdb9120d5d98ccd4cf7a71e642 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 @@ package com.android.systemui.statusbar;
 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.NotificationEntry;
 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 @@ public class NotificationMediaManager implements Dumpable {
     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 @@ public class NotificationMediaManager implements Dumpable {
     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 @@ public class NotificationMediaManager implements Dumpable {
     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 @@ public class NotificationMediaManager implements Dumpable {
                 Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata);
             }
             mMediaMetadata = metadata;
-            dispatchUpdateMediaMetaData(true /* changed */, true /* allowAnimation */);
+            dispatchUpdateMediaMetaData();
         }
     };
 
@@ -155,23 +115,13 @@ public class NotificationMediaManager implements Dumpable {
             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 class NotificationMediaManager implements Dumpable {
     public void onNotificationRemoved(String key) {
         if (key.equals(mMediaNotificationKey)) {
             clearCurrentMediaNotification();
-            dispatchUpdateMediaMetaData(true /* changed */, true /* allowEnterAnimation */);
+            dispatchUpdateMediaMetaData();
         }
     }
 
@@ -309,21 +259,18 @@ public class NotificationMediaManager implements Dumpable {
     }
 
     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 @@ public class NotificationMediaManager implements Dumpable {
                 Log.v(TAG, "DEBUG_MEDIA: insert listener, found new controller: "
                         + mMediaController + ", receive metadata: " + mMediaMetadata);
             }
-
-            metaDataChanged = true;
         }
 
         if (mediaNotification != null
@@ -371,8 +316,6 @@ public class NotificationMediaManager implements Dumpable {
                         + mMediaNotificationKey);
             }
         }
-
-        return metaDataChanged;
     }
 
     public void clearCurrentMediaNotification() {
@@ -380,10 +323,7 @@ public class NotificationMediaManager implements Dumpable {
         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 @@ public class NotificationMediaManager implements Dumpable {
         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 5dcf6d1d8f283d3ca16dbe2541752f58cd9f3388..f3b5ab6968a0cfab2e6475d460b7215fbc504917 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
@@ -31,11 +31,6 @@ public interface NotificationPresenter extends ExpandableNotificationRow.OnExpan
      */
     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 17da015789ea0ea64ffbb2c90884f039bcf5ecac..ffde8c03682fce777ea445029bfaeeb279956ee4 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 @@ public class RemoteInputController {
 
         // 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 125c8efe18849668181920677811ebcb88ec7133..1fe6b83b47b1ead64ce327cefa958d0eaa1b4d16 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.ActivityLaunchAnimator;
 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 @@ public interface CentralSurfacesDependenciesModule {
             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 bde298d7a33d4ebe909e104d8e28fd1e350274fa..ea1d7820c79c8391f54cfbe79513a3dd714a533a 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 @@ interface StatusEvent {
     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 @@ class BatteryEvent(@IntRange(from = 0, to = 100) val batteryLevel: Int) : Status
     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 @@ class ConnectedDisplayEvent : StatusEvent {
     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 @@ open class PrivacyEvent(override val showAnimation: Boolean = true) : StatusEven
     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 dccc23f61092cdbab47cf67bb312b6b849712b34..73c0bfec69a1d3135a02ec224d8e076f893e1927 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 @@ class SystemEventChipAnimationController @Inject constructor(
         })
     }
 
+    /** 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 7d866df73da97842580a9ba9acc7e21d720d1833..8ee1ade7a185812b6d6fd4cc4d8114c73a899d45 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 @@ constructor(
     }
 
     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 a3bc00248362d59c92d777abd1281e195173f2f4..f0e60dd2ce54fbf10373c058dcef13cb0cdfda10 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 @@ constructor(
         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 57d20246ea14e8a82ca21d2d3e2c9a45f2c558ab..f98f39e3532cd71e50023cc0b7bd59a998d084e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
@@ -20,6 +20,7 @@ import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
+import android.util.Log;
 import android.util.Property;
 import android.view.View;
 import android.view.animation.Interpolator;
@@ -33,6 +34,7 @@ import com.android.systemui.statusbar.notification.stack.ViewState;
  * An animator to animate properties
  */
 public class PropertyAnimator {
+    private static final String TAG = "PropertyAnimator";
 
     /**
      * Set a property on a view, updating its value, even if it's already animating.
@@ -114,18 +116,23 @@ public class PropertyAnimator {
                 || 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);
+                } else {
+                    Log.e(TAG, "Unexpected Animator set during onAnimationEnd. Not cleaning up.");
+                }
             }
         });
+        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 ff89c62ab49605420942c7e91c945e2bf3eb721d..23f87ba9dcc9468305731ed1c1b6d9d7d1426af3 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 @@ constructor(@NotificationRemoteInputLog private val logBuffer: LogBuffer) {
             }
         )
 
+    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 4573d5989faae09d80172ab9e8b5aba8d1ea9311..cfe9fbe3af296594a414a79e526f1cc4d9779301 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 @@ public final class NotificationEntry extends ListEntry {
         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/footer/shared/FooterViewRefactor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/FooterViewRefactor.kt
new file mode 100644
index 0000000000000000000000000000000000000000..94e70e5521c32019a61bb90d93b9f329cd76810e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/FooterViewRefactor.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.systemui.statusbar.notification.footer.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the FooterView refactor flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object FooterViewRefactor {
+    const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR
+
+    /** Is the refactor enabled */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.notificationsFooterViewRefactor()
+
+    /**
+     * Called to ensure code is only run when the flag is enabled. This protects users from the
+     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+     * build to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun isUnexpectedlyInLegacyMode() =
+        RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is enabled to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
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 59f10aed414530e211a769b16af292809c3832c3..daa4f1807625572a1079ff1aebd034a2bdfacbc5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -511,7 +511,6 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
             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 {
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 9fb6c1bb2fd0606c95c86b86ef7c273d7f29bedb..7cd32f9774220d186284def79e838794cb4caf6e 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.DisplayMetrics;
 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.ShadeSurface;
 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.bubbles.Bubbles;
 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 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
     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 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
             NotificationExpansionRepository notificationExpansionRepository,
             DozeParameters dozeParameters,
             ScrimController scrimController,
-            Lazy<LockscreenWallpaper> lockscreenWallpaperLazy,
             Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
             AuthRippleController authRippleController,
             DozeServiceHost dozeServiceHost,
@@ -770,7 +764,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
         mPowerManager = powerManager;
         mDozeParameters = dozeParameters;
         mScrimController = scrimController;
-        mLockscreenWallpaperLazy = lockscreenWallpaperLazy;
         mDozeScrimController = dozeScrimController;
         mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
         mAuthRippleController = authRippleController;
@@ -1198,10 +1191,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
 
         createNavigationBar(result);
 
-        if (ENABLE_LOCKSCREEN_WALLPAPER && mWallpaperSupported) {
-            mLockscreenWallpaper = mLockscreenWallpaperLazy.get();
-        }
-
         mAmbientIndicationContainer = getNotificationShadeWindowView().findViewById(
                 R.id.ambient_indication_container);
 
@@ -1268,24 +1257,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
                 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 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
         // 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 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
         // 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 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
         mRemoteInputManager.addControllerCallback(mStatusBarKeyguardViewManager);
 
         mLightBarController.setBiometricUnlockController(mBiometricUnlockController);
-        mMediaManager.setBiometricUnlockController(mBiometricUnlockController);
         Trace.endSection();
     }
 
@@ -1869,7 +1832,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
     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 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
         }
     };
 
-    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 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
         releaseGestureWakeLock();
         runLaunchTransitionEndRunnable();
         mKeyguardStateController.setLaunchTransitionFadingAway(false);
-        mPresenterLazy.get().updateMediaMetaData(true /* metaDataChanged */, true);
     }
 
     /**
@@ -2162,7 +2110,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
                 beforeFading.run();
             }
             updateScrimController();
-            mPresenterLazy.get().updateMediaMetaData(false, true);
             mShadeSurface.resetAlpha();
             mShadeSurface.fadeOut(
                     FADE_KEYGUARD_START_DELAY, FADE_KEYGUARD_DURATION,
@@ -3221,8 +3168,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
                     updateDozingState();
                     checkBarModes();
                     updateScrimController();
-                    mPresenterLazy.get()
-                            .updateMediaMetaData(false, mState != StatusBarState.KEYGUARD);
                     Trace.endSection();
                 }
 
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 6b4382f731eab9cbd18f22a622ff2f51cd114004..f4862c73606f1ba9f6b2f67487ee76c9ac0fb1d9 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 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp
         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/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 2960520f00b412caa5f5f6605a8d989cd60ff288..2206be5e614bb85bb3a7381e9bac10ab99a3bcc6 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 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
                     * (1.0f - mKeyguardHeadsUpShowingAmount);
         }
 
-        if (mSystemEventAnimator.isAnimationRunning()
-                && !mNotificationMediaManager.isLockscreenWallpaperOnNotificationShade()) {
+        if (mSystemEventAnimator.isAnimationRunning()) {
             newAlpha = Math.min(newAlpha, mSystemEventAnimatorAlpha);
         } else {
             mView.setTranslationX(0);
@@ -704,21 +703,11 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
 
     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 00fd9fbfffe3135fd3d1a4e36379120ca7885d80..0000000000000000000000000000000000000000
--- 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 54d81b83197eeca7ec65c9fd202855bdd61de81b..5a8b636e54fcfcefdc02620c3ca2401e845aca44 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 @@ public class PhoneStatusBarPolicy
         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 5b552640f3974ae871cc127c722aa14398961523..744d70e36972c151ab660e6f75ac6ff4534705c1 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.settingslib.Utils;
 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.domain.interactor.KeyguardTransitionInterac
 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 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
     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 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
                 mScrimBehind.setViewAlpha(mBehindAlpha);
             };
 
-    Consumer<TransitionStep> mPrimaryBouncerToGoneTransition;
+    Consumer<TransitionStep> mBouncerToGoneTransition;
 
     @Inject
     public ScrimController(
@@ -304,6 +309,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
+            AlternateBouncerToGoneTransitionViewModel alternateBouncerToGoneTransitionViewModel,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
             WallpaperRepository wallpaperRepository,
             @Main CoroutineDispatcher mainDispatcher,
@@ -349,6 +355,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
         });
         mColors = new GradientColors();
         mPrimaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel;
+        mAlternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel;
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
         mWallpaperRepository = wallpaperRepository;
         mMainDispatcher = mainDispatcher;
@@ -405,7 +412,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
         // 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 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
                     }
                 };
 
-        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 90fddd9ae22c0a2652d4bb6ae37b0d715f9a4638..267b56378d82b0de7fc04ba99d31242ef1b9fed5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -964,9 +964,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
             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
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 57a8e6fe0d91b795817a26ba8f52466248d4672d..a5cd062f6f37b00e310f25c412293cb0d8ec1c3d 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 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu
         // End old BaseStatusBar.userSwitched
         mCommandQueue.animateCollapsePanels();
         mMediaManager.clearCurrentMediaNotification();
-        updateMediaMetaData(true, false);
     }
 
     @Override
@@ -219,11 +218,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu
         return mNotificationPanel.isFullyCollapsed();
     }
 
-    @Override
-    public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
-        mMediaManager.updateMediaMetaData(metaDataChanged);
-    }
-
     @Override
     public void onExpandClicked(NotificationEntry clickedEntry, View clickedView,
             boolean 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 85fd2afed9ec46bdc89fcc73ead6aeb2a849d38e..d7f3b07675903327f477a631f71c9ef1ca320f71 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 @@ package com.android.systemui.statusbar.phone
 
 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)
+        setupWindow()
+        setupEdgeToEdge()
+        setCanceledOnTouchOutside(true)
+    }
 
+    private fun setupWindow() {
         window?.apply {
-            setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
-            addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
-
+            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)
-            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
-                )
+            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)
+    }
 
-                val lp = attributes
-                lp.fitInsetsSides = 0
-                lp.horizontalMargin = 0f
-                attributes = lp
+    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()
             }
         }
-        setCanceledOnTouchOutside(true)
-    }
 }
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 9d627af357cb407729efce0a49ab6c2468920bd4..2558645e3f649a9922c60d463926ebaf2597631a 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.Dependency;
 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 com.android.systemui.util.DialogKt;
 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 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
 
     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 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
                 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 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
 
     @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 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
 
             updateWindowSize();
         }
+
+        mDelegate.onConfigurationChanged(this, configuration);
     }
 
     /**
@@ -212,7 +308,9 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
      * 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 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
      * 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 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
         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 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
         }
     }
 
+    /**
+     * 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/unfold/UnfoldLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt
index 9269df31e37ed408f2516b0f318d421bc19ad78c..8c66c2f0fab34c656f63a2fdba024c372820d3f1 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 @@ package com.android.systemui.unfold
 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 @@ constructor(
     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 @@ constructor(
         // 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 @@ constructor(
         }
 
         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 @@ constructor(
         }
     }
 
+    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 @@ constructor(
             // 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 @@ constructor(
 }
 
 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 0000000000000000000000000000000000000000..ed960f31228a35d864afe5f923d9e50784808e75
--- /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 ed3eacd27c0ac0e68e7efe9ca70a5d7e2cd11e39..71314f1f17759049b4c47b37620c436ec88df593 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 @@ package com.android.systemui.unfold
 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.UnfoldOnlyProgressProvider
 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 @@ class UnfoldTransitionModule {
                 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/util/kotlin/Flow.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
index 83ff789808801d1a4f19e7b0891277a70b6afbfb..b3834f58be2f865fff83d72a576d3b9998f4d5c1 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 @@ inline fun <T> CoroutineScope.stateFlow(
     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/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index aea3030967d281ef23fa9c5fd680a46e548836e0..fdf5966419b44eea7ce0a6c891caea2b214b0cc2 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -126,8 +126,6 @@ public class ImageWallpaper extends WallpaperService {
         private int mBitmapUsages = 0;
         private final Object mLock = new Object();
 
-        private boolean mIsLockscreenLiveWallpaperEnabled;
-
         CanvasEngine() {
             super();
             setFixedSizeAllowed(true);
@@ -171,12 +169,8 @@ public class ImageWallpaper extends WallpaperService {
                 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);
@@ -327,10 +321,8 @@ public class ImageWallpaper extends WallpaperService {
             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!");
@@ -341,18 +333,11 @@ public class ImageWallpaper extends WallpaperService {
                 // 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;
@@ -373,9 +358,7 @@ public class ImageWallpaper extends WallpaperService {
                     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/tests/src/com/android/SysUITestModule.kt b/packages/SystemUI/tests/src/com/android/SysUITestModule.kt
index ea74510a907050a4aa87591532fdfd1f40edd72c..0b3c3b483d1c50fc1e62fbd1302b66c83a5242fa 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 @@ class SysUITestModule {
 
     @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 167e3417c1624b5c234308a847b3ea624b0bc621..ff1d5b2ea3bfc3f2004c7b8bd8bd78ed1b564a3c 100644
--- a/packages/SystemUI/tests/src/com/android/TestMocksModule.kt
+++ b/packages/SystemUI/tests/src/com/android/TestMocksModule.kt
@@ -22,7 +22,9 @@ import android.util.DisplayMetrics
 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.LogBuffer
 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 @@ data class TestMocksModule(
     @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/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index df7d1b7a4aadb2db06b397b9c4fd0a97e5d94cb0..aed795a440b48acac03eadd4797d3bee6e14a3b9 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
@@ -222,14 +222,14 @@ public class MenuViewLayerTest extends SysuiTestCase {
 
     @Test
     public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme() {
-        final float menuTop = IME_TOP + 100;
-        mMenuAnimationController.moveAndPersistPosition(new PointF(0, menuTop));
+        mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 100));
+        final PointF beforePosition = mMenuView.getMenuPosition();
 
         dispatchShowingImeInsets();
 
         final float menuBottom = mMenuView.getTranslationY() + mMenuView.getMenuHeight();
-        assertThat(mMenuView.getTranslationX()).isEqualTo(0);
-        assertThat(menuBottom).isLessThan(IME_TOP);
+        assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x);
+        assertThat(menuBottom).isLessThan(beforePosition.y);
     }
 
     @Test
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 215d63508306674c772647766d7a7058bb9b7460..0000000000000000000000000000000000000000
--- 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 d68a3131da4c286e36be1017bb2bbe40b9728d10..8c26776a1ef8a67c29b8708272805adbc016790c 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 @@ class AuthDialogPanelInteractionDetectorTest : SysuiTestCase() {
             runCurrent()
 
             // WHEN shade expands
+            shadeRepository.setLegacyShadeTracking(true)
             shadeRepository.setLegacyShadeExpansion(.5f)
             runCurrent()
 
@@ -108,6 +109,7 @@ class AuthDialogPanelInteractionDetectorTest : SysuiTestCase() {
 
             // 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 9f24a9f553a16cd66486ea3267b3ffaca9ab7338..15633d1baed1ce18894487d7ca6bd9ff72328247 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 @@ import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
 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 @@ internal fun fingerprintSensorPropertiesInternal(
             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/ui/viewmodel/PromptFingerprintIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt
deleted file mode 100644
index fd86486aeff87bb71c1eebd16ffeae6dce9c6e5a..0000000000000000000000000000000000000000
--- 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 ca6df4027ea9a15346eb99ef45d4a8bea490a2c2..b695a0ee1fa36dd8e0eaf4b752c8639b8ea6d6f0 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.faceSensorPropertiesInternal
 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 @@ import org.mockito.junit.MockitoJUnit
 
 private const val USER_ID = 4
 private const val CHALLENGE = 2L
+private const val DELAY = 1000L
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -88,11 +94,22 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
 
     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 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
 
         viewModel =
             PromptViewModel(displayStateInteractor, selector, vibrator, mContext, featureFlags)
+        iconViewModel = viewModel.iconViewModel
         featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
     }
 
@@ -123,7 +141,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
             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 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
             }
             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 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
             assertThat(authenticated?.isNotAuthenticated).isTrue()
             assertThat(size).isEqualTo(expectedSize)
             assertButtonsVisible(negative = expectedSize != PromptSize.SMALL)
-            assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATING)
         }
 
     @Test
@@ -205,6 +220,472 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
         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 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
         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 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
         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 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
                     PromptSize.SMALL
                 }
             )
-        assertThat(legacyState)
-            .isEqualTo(
-                if (expectConfirmation) {
-                    BiometricState.STATE_PENDING_CONFIRMATION
-                } else {
-                    BiometricState.STATE_AUTHENTICATED
-                }
-            )
+
         assertButtonsVisible(
             cancel = expectConfirmation,
             confirm = expectConfirmation,
@@ -298,7 +769,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
         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 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
         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 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
             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 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
         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 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
 
         assertThat(authenticating).isFalse()
         assertThat(authenticated?.isAuthenticated).isTrue()
-        assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED)
         assertThat(canTryAgain).isFalse()
     }
 
@@ -524,7 +980,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
         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 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
         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 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
             viewModel.confirmAuthenticated()
             assertThat(message).isEqualTo(PromptMessage.Empty)
             assertButtonsVisible()
-        } else {
-            assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED)
         }
     }
 
@@ -563,7 +1014,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
         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 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
 
         assertThat(authenticating).isFalse()
         assertThat(authenticated?.isAuthenticated).isTrue()
-        assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED)
         assertThat(canTryAgain).isFalse()
     }
 
@@ -610,12 +1059,10 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
         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 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
         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 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
         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()
@@ -784,6 +1226,15 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
                     fingerprint = fingerprintSensorPropertiesInternal(strong = true).first(),
                     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,
@@ -794,6 +1245,16 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
                     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 @@ internal data class TestCase(
         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 @@ internal data class TestCase(
     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 @@ private fun PromptSelectorInteractor.initializePrompt(
         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/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
index 9373ada75003b81b1c344bae977614bc8888a581..f6b284fffa3dd7c9742c9e0475a5cdba29d0f33c 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
@@ -110,6 +110,27 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
         resources = context.orCreateTestableResources
     }
 
+    @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)
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 80b281e458d0b923cec9e919feb98db42dcc2a08..882bcab5fab6d45ed69596de9273b71f1437791b 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;
@@ -104,21 +103,6 @@ public class SysuiColorExtractorTests extends SysuiTestCase {
         }
     }
 
-    @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);
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 91409a37655685dec6049d9b49857dfe6454b0ac..fcb191b4cbd60fa48b27e0fd2d97e071ffdc32a6 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 androidx.test.filters.SmallTest
 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.junit.runner.RunWith
 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 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
 
     @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 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
 
     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 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
             job.cancel()
             runCurrent()
 
-            Mockito.verify(broadcastDispatcher).unregisterReceiver(receiver)
+            verify(broadcastDispatcher).unregisterReceiver(receiver)
         }
 
     @Test
@@ -166,7 +179,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
             installedProviders(listOf(stopwatchProviderInfo))
             val repository = initCommunalWidgetRepository()
             collectLastValue(repository.stopwatchAppWidgetInfo)()
-            Mockito.verify(appWidgetHost).allocateAppWidgetId()
+            verify(appWidgetHost).allocateAppWidgetId()
         }
 
     @Test
@@ -185,8 +198,8 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
 
             // 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 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
 
             // Verify app widget id deleted
             assertThat(lastStopwatchProviderInfo()).isNull()
-            Mockito.verify(appWidgetHost).deleteAppWidgetId(123456)
+            verify(appWidgetHost).deleteAppWidgetId(123456)
         }
 
     @Test
@@ -203,13 +216,13 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
             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 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
             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 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
             assertThat(
                     listOf(
                         CommunalWidgetMetadata(
-                            componentName = componentName1,
+                            componentName = fakeAllowlist[0],
+                            priority = 3,
+                            sizes = listOf(CommunalContentSize.HALF),
+                        ),
+                        CommunalWidgetMetadata(
+                            componentName = fakeAllowlist[1],
                             priority = 2,
-                            sizes = listOf(CommunalContentSize.HALF)
+                            sizes = listOf(CommunalContentSize.HALF),
                         ),
                         CommunalWidgetMetadata(
-                            componentName = componentName2,
+                            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 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
     }
 
     private fun verifyBroadcastReceiverRegistered() {
-        Mockito.verify(broadcastDispatcher)
+        verify(broadcastDispatcher)
             .registerReceiver(
                 any(),
                 any(),
@@ -284,7 +356,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
     }
 
     private fun verifyBroadcastReceiverNeverRegistered() {
-        Mockito.verify(broadcastDispatcher, Mockito.never())
+        verify(broadcastDispatcher, Mockito.never())
             .registerReceiver(
                 any(),
                 any(),
@@ -297,7 +369,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
 
     private fun broadcastReceiverUpdate(): BroadcastReceiver {
         val broadcastReceiverCaptor = kotlinArgumentCaptor<BroadcastReceiver>()
-        Mockito.verify(broadcastDispatcher)
+        verify(broadcastDispatcher)
             .registerReceiver(
                 broadcastReceiverCaptor.capture(),
                 any(),
@@ -310,7 +382,11 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
         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 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
         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 cdc42e09683080583ecf56d0b03179a333a324f5..8e21f294a3615a74ab545bd8affbec2e07650cc3 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 androidx.test.filters.SmallTest
 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/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
index 7a13a0a96ee62862ada4b0aca3f3df0ab2f04b22..489665cd130aabf5d866294e1bddc1315b09bee4 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 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() {
 
         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/ui/viewmodel/BouncerToGoneFlowsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..1ff46db9962460b33be6b81e8b4e49ee5770e561
--- /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 d7802aabb298dad4c60c0a88753800042f546315..6cab023d59b0f1beb8facbded15c9f33e33aff54 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.data.repository.FakeKeyguardTransitionRepos
 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.android.systemui.util.mockito.whenever
 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 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() {
     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 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() {
                 primaryBouncerInteractor,
                 keyguardDismissActionInteractor,
                 featureFlags,
+                bouncerToGoneFlows,
             )
 
         whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(false)
@@ -148,59 +153,6 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() {
             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/mediaprojection/MediaProjectionMetricsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..429aad113a9a414e7ebdfb26b69fa8453ebb217c
--- /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/tiles/CameraToggleTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
index 0552ced1d678df328db13f83923c1415781f3ed5..0e4b113f57ca842068a88363f0a1182bf97b9e33 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 @@ class CameraToggleTileTest : SysuiTestCase() {
     }
 
     @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 0fcfdb6f318fe1469bdc47630413fbe93284a59d..b98a7570bb6c9d0eed5d0c9d8daf902d5da44ba9 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 @@ class MicrophoneToggleTileTest : SysuiTestCase() {
     }
 
     @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/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index 90d2e78a411fd7480aa774ac828fe8fb1f54fc87..c439cfe6270fd87e08fd407b058af3cc664f2298 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 junit.framework.Assert.assertTrue;
 
 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 @@ import org.mockito.MockitoAnnotations;
  */
 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 @@ public class RecordingControllerTest extends SysuiTestCase {
     @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 @@ public class RecordingControllerTest extends SysuiTestCase {
         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 @@ public class RecordingControllerTest extends SysuiTestCase {
                 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/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 4f3216da737022085f30cc34e664af605b94fa55..b421e1b454773e5c82e2cea122d06d8c769858fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -21,6 +21,7 @@ import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.view.KeyEvent
 import android.view.MotionEvent
+import android.view.View
 import android.view.ViewGroup
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardMessageAreaController
@@ -43,11 +44,20 @@ import com.android.systemui.bouncer.ui.BouncerView
 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.data.repository.FakeCommunalRepository
+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
@@ -83,6 +93,7 @@ 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
@@ -97,6 +108,7 @@ import org.mockito.Mock
 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
@@ -135,6 +147,8 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
     @Mock
     private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener
     @Mock private lateinit var notificationInsetsController: NotificationInsetsController
+    @Mock private lateinit var mCommunalViewModel: CommunalViewModel
+    private lateinit var mCommunalRepository: FakeCommunalRepository
     @Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
     @Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
     @Mock lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController
@@ -159,7 +173,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
 
     private lateinit var testScope: TestScope
 
-    private lateinit var featureFlags: FakeFeatureFlags
+    private lateinit var featureFlagsClassic: FakeFeatureFlagsClassic
 
     @Before
     fun setUp() {
@@ -174,14 +188,16 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
         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)
+
+        mCommunalRepository = FakeCommunalRepository()
 
         testScope = TestScope()
         fakeClock = FakeSystemClock()
@@ -216,14 +232,16 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
                 mock(KeyguardMessageAreaController.Factory::class.java),
                 keyguardTransitionInteractor,
                 primaryBouncerToGoneTransitionViewModel,
+                mCommunalViewModel,
+                mCommunalRepository,
                 notificationExpansionRepository,
-                featureFlags,
+                featureFlagsClassic,
                 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,
@@ -443,7 +461,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
         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()
@@ -460,7 +478,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
         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()
@@ -473,6 +491,48 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
             verify(view).findViewById<ViewGroup>(R.id.keyguard_message_area)
         }
 
+    @Test
+    fun setsUpCommunalHubLayout_whenFlagEnabled() {
+        if (!isComposeAvailable()) {
+            return
+        }
+
+        mCommunalRepository.setIsCommunalEnabled(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
+        }
+
+        mCommunalRepository.setIsCommunalEnabled(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)
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 4d3eab45d0010e068236297c15bd4f5a5d367a16..9c571015f750da7b6f96896309b762e488605f84 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -42,6 +42,8 @@ import com.android.systemui.bouncer.ui.BouncerView
 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.data.repository.FakeCommunalRepository
+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
@@ -142,6 +144,8 @@ class NotificationShadeWindowViewTest : SysuiTestCase() {
     private lateinit var unfoldTransitionProgressProvider:
         Optional<UnfoldTransitionProgressProvider>
     @Mock private lateinit var notificationInsetsController: NotificationInsetsController
+    @Mock private lateinit var mCommunalViewModel: CommunalViewModel
+    private lateinit var mCommunalRepository: FakeCommunalRepository
     @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     @Mock lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
     @Mock lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
@@ -178,6 +182,8 @@ class NotificationShadeWindowViewTest : SysuiTestCase() {
         whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition)
             .thenReturn(emptyFlow())
 
+        mCommunalRepository = FakeCommunalRepository()
+
         val featureFlags = FakeFeatureFlags()
         featureFlags.set(Flags.TRACKPAD_GESTURE_COMMON, true)
         featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false)
@@ -218,6 +224,8 @@ class NotificationShadeWindowViewTest : SysuiTestCase() {
                 Mockito.mock(KeyguardMessageAreaController.Factory::class.java),
                 keyguardTransitionInteractor,
                 primaryBouncerToGoneTransitionViewModel,
+                mCommunalViewModel,
+                mCommunalRepository,
                 NotificationExpansionRepository(),
                 featureFlags,
                 FakeSystemClock(),
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 81382a44def6eb9ca0dee2656dd132030077146c..3a260ae374c63d5f53bdfde64e333140113dd422 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
@@ -444,105 +444,6 @@ class ShadeInteractorTest : SysuiTestCase() {
             assertThat(underTest.anyExpansion.value).isEqualTo(.5f)
         }
 
-    @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() {
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 a5f5fc7e36f2afc46a6d74afb84f00a964d0d26a..43adc69be13fd021f7dd42c2c9a0e0e51ed159d7 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.NotificationManager.VISIBILITY_NO_OVERRIDE;
 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 @@ import java.util.concurrent.Executor;
 @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 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
         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 cfcf4257ce28c2f3d27d325f36fda847e132dd07..0000000000000000000000000000000000000000
--- 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 839770267c749ab3b44b27ce3d91dc981c1b75bf..df8afde1b9a36917e1a7010ec9156face0e16ae5 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 @@ class FakeStatusEvent(
     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 4fcccf887e587c3af624a5980703e19c7f3436ac..fee8b82a3038acc543da9d93d437c9ccc6ac8a3a 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.BatteryStatusChip
 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
@@ -369,6 +371,63 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
         verify(listener, times(1)).onHidePersistentDot()
     }
 
+    @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
@@ -572,6 +631,20 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
         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 c664c39f443265b10b7e6a552c068f091d6a3217..2ef4374ce13ab6c59bcb0deda83c5d7d9d7f55e3 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 @@ package com.android.systemui.statusbar.notification;
 
 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.View;
 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 @@ public class PropertyAnimatorTest extends SysuiTestCase {
             return mEffectiveProperty;
         }
     };
-    private AnimatorListenerAdapter mFinishListener = mock(AnimatorListenerAdapter.class);
+    private AnimatorListenerAdapter mFinishListener;
     private AnimationProperties mAnimationProperties = new AnimationProperties() {
         @Override
         public AnimationFilter getAnimationFilter() {
@@ -104,6 +110,7 @@ public class PropertyAnimatorTest extends SysuiTestCase {
     @Before
     public void setUp() {
         mView = new View(getContext());
+        mFinishListener = mock(AnimatorListenerAdapter.class);
     }
 
     @Test
@@ -228,6 +235,32 @@ public class PropertyAnimatorTest extends SysuiTestCase {
         assertTrue(animator.getListeners().contains(mFinishListener));
     }
 
+    @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();
@@ -236,4 +269,4 @@ public class PropertyAnimatorTest extends SysuiTestCase {
         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/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index c8cbe42fb0d54915d8157be70c230da7d818517a..a59cd87d78da769e34ac3a2f21334bf81630d19b 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 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
             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 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
         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 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
                 new NotificationExpansionRepository(),
                 mDozeParameters,
                 mScrimController,
-                mLockscreenWallpaperLazy,
                 mBiometricUnlockControllerLazy,
                 mAuthRippleController,
                 mDozeServiceHost,
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 47671fbadd0a6aae302d7e21c154aa566d2b2c46..0000000000000000000000000000000000000000
--- 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 6b3bd22d5e625fd0243323b33a99394b19d8e6e4..15c09b53938f50cb4476b41bb5ad3b87d07fbb07 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.domain.interactor.KeyguardTransitionInterac
 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 @@ public class ScrimControllerTest extends SysuiTestCase {
     @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 @@ public class ScrimControllerTest extends SysuiTestCase {
         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 @@ public class ScrimControllerTest extends SysuiTestCase {
                 mKeyguardUnlockAnimationController,
                 mStatusBarKeyguardViewManager,
                 mPrimaryBouncerToGoneTransitionViewModel,
+                mAlternateBouncerToGoneTransitionViewModel,
                 mKeyguardTransitionInteractor,
                 mWallpaperRepository,
                 mMainDispatcher,
@@ -992,6 +998,7 @@ public class ScrimControllerTest extends SysuiTestCase {
                 mKeyguardUnlockAnimationController,
                 mStatusBarKeyguardViewManager,
                 mPrimaryBouncerToGoneTransitionViewModel,
+                mAlternateBouncerToGoneTransitionViewModel,
                 mKeyguardTransitionInteractor,
                 mWallpaperRepository,
                 mMainDispatcher,
@@ -1775,7 +1782,7 @@ public class ScrimControllerTest extends SysuiTestCase {
     @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 @@ public class ScrimControllerTest extends SysuiTestCase {
     @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/SystemUIBottomSheetDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..21d22bcf3d2afc4354b279e304b2b87153248c9b
--- /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/unfold/updates/DeviceStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceStateRepositoryTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..4eb159103b49bdf3d496c56a8cc347038147d0c4
--- /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 0000000000000000000000000000000000000000..065132300564a0360913b6728959d1cc238d0732
--- /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/wallpapers/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
index 468c5a73645b82032078688c815f67d225520ee4..fc2030f694ad16659014bf349ba609957e384e7e 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 @@ public class ImageWallpaperTest extends SysuiTestCase {
         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 @@ public class ImageWallpaperTest extends SysuiTestCase {
         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 @@ public class ImageWallpaperTest extends SysuiTestCase {
     }
 
     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 @@ public class ImageWallpaperTest extends SysuiTestCase {
         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/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 30132f7747b7e2356d1658a466bb3b7893b72fbb..08adda32eb6db8ad9dcb32ad65aee91e16a89415 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 @@ class FakeCommunalWidgetRepository : CommunalWidgetRepository {
     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/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
index 5ffc094b88b3de70900130a21fc51576d8556b3f..7473ca6a64861d89f163fc1d2b5f754bb7dc314e 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.PhysicsBasedUnfoldTransitionProgress
 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 @@ class UnfoldSharedModule {
     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 6743515c2ec7d12ee69e7ad650b3eb9f7ac271d2..003013e185832c00c69ba76cdc12296ddb4f8e61 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 @@ package com.android.systemui.unfold.updates
 
 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 @@ constructor(
                     "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 6e87beeb295f78a1cae2f699ca6edd29666b1af8..ea6786e6c4bcce7d0d78fd5baf308489854963e8 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 @@ interface FoldProvider {
     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 0000000000000000000000000000000000000000..61b0b40a55bfcf80103719ecd81e38fc307b3032
--- /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 3406102b28ac4d0fc6e1a5c149060a7d979ba70f..98421a9e1d3e62d39b54b6bdc6284199d6f0bfd2 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -367,11 +367,7 @@ public class WallpaperBackupAgent extends BackupAgent {
             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 @@ public class WallpaperBackupAgent extends BackupAgent {
             // 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 @@ public class WallpaperBackupAgent extends BackupAgent {
     }
 
     @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 @@ public class WallpaperBackupAgent extends BackupAgent {
         // 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 @@ public class WallpaperBackupAgent extends BackupAgent {
                     // 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 @@ public class WallpaperBackupAgent extends BackupAgent {
 
                 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 dc1126edde410fd1924b9f363f99dd263f099c0e..4c224fb33b26dc3b65cd91638b6588b0cd112d8c 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 @@ public class WallpaperBackupAgentTest {
     @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 @@ public class WallpaperBackupAgentTest {
     @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 @@ public class WallpaperBackupAgentTest {
         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 @@ public class WallpaperBackupAgentTest {
         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 @@ public class WallpaperBackupAgentTest {
         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 @@ public class WallpaperBackupAgentTest {
     }
 
     @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 @@ public class WallpaperBackupAgentTest {
 
         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 @@ public class WallpaperBackupAgentTest {
         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 @@ public class WallpaperBackupAgentTest {
         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 @@ public class WallpaperBackupAgentTest {
         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 @@ public class WallpaperBackupAgentTest {
         }
 
         @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 1a735f89a2bc38109135e151fd60a48b8dab2358..75ecdb78fe007633be28309f63ebf71ecca3291a 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -1,38 +1,40 @@
 package: "com.android.server.accessibility"
 
+# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
+
 flag {
-    name: "proxy_use_apps_on_virtual_device_listener"
+    name: "add_window_token_without_lock"
     namespace: "accessibility"
-    description: "Fixes race condition described in b/286587811"
-    bug: "286587811"
+    description: "Calls WMS.addWindowToken without holding A11yManagerService#mLock"
+    bug: "297972548"
 }
 
 flag {
-    name: "enable_magnification_multiple_finger_multiple_tap_gesture"
+    name: "deprecate_package_list_observer"
     namespace: "accessibility"
-    description: "Whether to enable multi-finger-multi-tap gesture for magnification"
-    bug: "257274411"
+    description: "Stops using the deprecated PackageListObserver."
+    bug: "304561459"
 }
 
 flag {
-    name: "enable_magnification_joystick"
+    name: "disable_continuous_shortcut_on_force_stop"
     namespace: "accessibility"
-    description: "Whether to enable joystick controls for magnification"
-    bug: "297211257"
+    description: "When a package is force stopped, remove the button shortcuts of any continuously-running shortcuts."
+    bug: "198018180"
 }
 
 flag {
-    name: "send_a11y_events_based_on_state"
+    name: "enable_magnification_joystick"
     namespace: "accessibility"
-    description: "Sends accessibility events in TouchExplorer#onAccessibilityEvent based on internal state to keep it consistent. This reduces test flakiness."
-    bug: "295575684"
+    description: "Whether to enable joystick controls for magnification"
+    bug: "297211257"
 }
 
 flag {
-    name: "add_window_token_without_lock"
+    name: "enable_magnification_multiple_finger_multiple_tap_gesture"
     namespace: "accessibility"
-    description: "Calls WMS.addWindowToken without holding A11yManagerService#mLock"
-    bug: "297972548"
+    description: "Whether to enable multi-finger-multi-tap gesture for magnification"
+    bug: "257274411"
 }
 
 flag {
@@ -43,17 +45,17 @@ flag {
 }
 
 flag {
-    name: "disable_continuous_shortcut_on_force_stop"
+    name: "proxy_use_apps_on_virtual_device_listener"
     namespace: "accessibility"
-    description: "When a package is force stopped, remove the button shortcuts of any continuously-running shortcuts."
-    bug: "198018180"
+    description: "Fixes race condition described in b/286587811"
+    bug: "286587811"
 }
 
 flag {
-    name: "deprecate_package_list_observer"
+    name: "reduce_touch_exploration_sensitivity"
     namespace: "accessibility"
-    description: "Stops using the deprecated PackageListObserver."
-    bug: "304561459"
+    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 {
@@ -64,8 +66,8 @@ flag {
 }
 
 flag {
-    name: "reduce_touch_exploration_sensitivity"
+    name: "send_a11y_events_based_on_state"
     namespace: "accessibility"
-    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"
+    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/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index e65a185adfd2c53044f90c9809d55e34089e8c34..87f9cf10f824b928f5fca143488e27a70e9ac7fc 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -4929,8 +4929,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
         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);
@@ -4987,7 +4987,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
         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);
@@ -5035,7 +5035,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
                     if (readTouchExplorationEnabledSettingLocked(userState)) {
                         onUserStateChangedLocked(userState);
                     }
-                } else if (mDisplayMagnificationEnabledUri.equals(uri)) {
+                } else if (mMagnificationmSingleFingerTripleTapEnabledUri.equals(uri)) {
                     if (readMagnificationEnabledSettingsLocked(userState)) {
                         onUserStateChangedLocked(userState);
                     }
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 898cdcc30e0f11edf7a719fb9683dd1f25739875..4dca5a5c807b8f5d481f1b151bdf179e09bf3bcf 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -148,6 +148,7 @@ java_library_static {
         "android.hardware.light-V2.0-java",
         "android.hardware.gnss-V2-java",
         "android.hardware.vibrator-V2-java",
+        "android.nfc.flags-aconfig-java",
         "app-compat-annotations",
         "framework-tethering.stubs.module_lib",
         "service-art.stubs.system_server",
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index beea063221fb197625e7388a5dbca80632f0550f..8624dd5f3e6bf1a091e3eeaf8ee6c59fb0e5a97a 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1236,7 +1236,7 @@ class StorageManagerService extends IStorageManager.Stub
     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 @@ class StorageManagerService extends IStorageManager.Stub
                 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 @@ class StorageManagerService extends IStorageManager.Stub
         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 @@ class StorageManagerService extends IStorageManager.Stub
 
         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 @@ class StorageManagerService extends IStorageManager.Stub
 
         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 @@ class StorageManagerService extends IStorageManager.Stub
 
         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 @@ class StorageManagerService extends IStorageManager.Stub
 
         @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 @@ class StorageManagerService extends IStorageManager.Stub
         @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 @@ class StorageManagerService extends IStorageManager.Stub
 
         @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 55aa7164a67bbd2610f4c68032bf205fb4a7f1ed..003046ab884f7f784677490a31dca9328e664233 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -250,7 +250,7 @@ public class Watchdog implements Dumpable {
         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 @@ public class Watchdog implements Dumpable {
          * @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 @@ public class Watchdog implements Dumpable {
 
             mCompleted = false;
             mCurrentMonitor = null;
-            mStartTimeMillis = SystemClock.uptimeMillis();
+            mStartTimeMillis = nowMillis;
+            mPauseEndTimeMillis = 0;
             mHandler.postAtFrontOfQueue(this);
         }
 
@@ -360,20 +360,19 @@ public class Watchdog implements Dumpable {
         }
 
         /**
-         * Sets the timeout of the HandlerChecker for one run.
-         *
-         * <p>The current run will be ignored and the next run will be set to this timeout.
+         * Pauses the checks for the given time.
          *
-         * <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 @@ public class Watchdog implements Dumpable {
     }
 
      /**
-     * 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 @@ public class Watchdog implements Dumpable {
      * 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 9b5f18caf71a42632bce63a4bccbfaebe7f27c4b..710278d6b3c604a7ad910d9261f53577d9560dfa 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 @@ final class AppWaitingForDebuggerDialog extends BaseErrorDialog {
         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/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index e0e6cade5f2770643c72dee8d9c6fc3f023f6966..59d8e7e96ba62283dbb42366bdc96eeb01f9394b 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -869,6 +869,8 @@ public final class ProcessList {
                                 ApplicationExitInfo.REASON_LOW_MEMORY,
                                 ApplicationExitInfo.SUBREASON_OOM_KILL,
                                 "oom");
+
+                            oomKill.logKillOccurred();
                         }
                     }
                 }
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index bb9ea285385b13c57839a8096b29e1d21cb4bbed..cbaf05bb4e231e788bcbd88b9bf70219b8d09de4 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -15,3 +15,10 @@ flag {
      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 ba43c8df92c5d9476a0b9b538e95383bcd1a92a4..292fc14ac6eb1e51e9edce1565b0952f4b1fc906 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 @@ import java.util.Objects;
      * {@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/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 35260ed6f14800d4985d1ad7d3a3cabeaaac0518..7abd9c7f750b9aea2dd3d0f01775096b157c402d 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.ISpatializerHeadTrackingCallback;
 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 @@ public class SpatializerHelper {
 
     /*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 @@ public class SpatializerHelper {
                 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 @@ public class SpatializerHelper {
             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 @@ public class SpatializerHelper {
             // 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 @@ public class SpatializerHelper {
 
         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 @@ public class SpatializerHelper {
         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 @@ public class SpatializerHelper {
             // 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 @@ public class SpatializerHelper {
         // 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 @@ public class SpatializerHelper {
     }
 
     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 @@ public class SpatializerHelper {
     // 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 @@ public class SpatializerHelper {
     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 @@ public class SpatializerHelper {
 
     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 3d347bea6bae94302b8020935db898425057db3e..f9bc8dcc7d0bf1ca2770144308f949a3b4e082fa 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.UsbDevice;
 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 @@ public class CameraServiceProxy extends SystemService
      * 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 @@ public class CameraServiceProxy extends SystemService
     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 @@ public class CameraServiceProxy extends SystemService
         }
     }
 
-    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 @@ public class CameraServiceProxy extends SystemService
         }
     }
 
+    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/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
index 425a1af1bbaa3ef87dfa4fc68f03d082de354e42..669580189d7c402f842018c52f980149f25f2c06 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 @@ import java.util.Objects;
  * 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 @@ public final class DisplayBrightnessState {
 
     private final boolean mIsSlowChange;
 
+    private final float mCustomAnimationRate;
+
     private DisplayBrightnessState(Builder builder) {
         mBrightness = builder.getBrightness();
         mSdrBrightness = builder.getSdrBrightness();
@@ -45,6 +49,7 @@ public final class DisplayBrightnessState {
         mShouldUseAutoBrightness = builder.getShouldUseAutoBrightness();
         mIsSlowChange = builder.isSlowChange();
         mMaxBrightness = builder.getMaxBrightness();
+        mCustomAnimationRate = builder.getCustomAnimationRate();
     }
 
     /**
@@ -97,7 +102,12 @@ public final class DisplayBrightnessState {
         return mMaxBrightness;
     }
 
-
+    /**
+     * @return custom animation rate
+     */
+    public float getCustomAnimationRate() {
+        return mCustomAnimationRate;
+    }
 
     @Override
     public String toString() {
@@ -112,6 +122,7 @@ public final class DisplayBrightnessState {
         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 @@ public final class DisplayBrightnessState {
                         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 @@ public final class DisplayBrightnessState {
         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 @@ public final class DisplayBrightnessState {
             builder.setShouldUseAutoBrightness(state.getShouldUseAutoBrightness());
             builder.setIsSlowChange(state.isSlowChange());
             builder.setMaxBrightness(state.getMaxBrightness());
+            builder.setCustomAnimationRate(state.getCustomAnimationRate());
             return builder;
         }
 
@@ -303,6 +317,22 @@ public final class DisplayBrightnessState {
             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 cb2302a602485f4d474be0745c4d1226a8c84365..e5f01df75fcdfa79615a2cb261454802d4cd4db4 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 @@ public final class DisplayManagerService extends SystemService {
         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 @@ public final class DisplayManagerService extends SystemService {
 
     @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 @@ public final class DisplayManagerService extends SystemService {
             // 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 @@ public final class DisplayManagerService extends SystemService {
     // 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);
@@ -2991,7 +2999,11 @@ public final class DisplayManagerService extends SystemService {
                     + 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 @@ public final class DisplayManagerService extends SystemService {
     }
 
     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 @@ public final class DisplayManagerService extends SystemService {
 
         @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 final class DisplayManagerService extends SystemService {
          */
         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/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index d97c8e71c73c34bdc6989930f3a539ba79e494d9..8c39d7de54f7923abcde3568be395c629bfb04f7 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -16,6 +16,13 @@
 
 package com.android.server.display;
 
+import static android.view.Display.TYPE_EXTERNAL;
+import static android.view.Display.TYPE_INTERNAL;
+import static android.view.Display.TYPE_OVERLAY;
+import static android.view.Display.TYPE_UNKNOWN;
+import static android.view.Display.TYPE_VIRTUAL;
+import static android.view.Display.TYPE_WIFI;
+
 import android.content.Context;
 import android.content.Intent;
 import android.hardware.display.DisplayManager;
@@ -26,7 +33,10 @@ import android.view.Display;
 import com.android.server.display.feature.DisplayManagerFlags;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
 
 class DisplayManagerShellCommand extends ShellCommand {
     private static final String TAG = "DisplayManagerShellCommand";
@@ -153,9 +163,12 @@ class DisplayManagerShellCommand extends ShellCommand {
         pw.println("    Sets the user disabled HDR types as TYPES");
         pw.println("  get-user-disabled-hdr-types");
         pw.println("    Returns the user disabled HDR types");
-        pw.println("  get-displays [CATEGORY]");
+        pw.println("  get-displays [-c|--category CATEGORY] [-i|--ids-only] [-t|--type TYPE]");
+        pw.println("    [CATEGORY]");
         pw.println("    Returns the current displays. Can specify string category among");
         pw.println("    DisplayManager.DISPLAY_CATEGORY_*; must use the actual string value.");
+        pw.println("    Can choose to print only the ids of the displays. " +  "Can filter by");
+        pw.println("    display types. For example, '--type external'");
         pw.println("  dock");
         pw.println("    Sets brightness to docked + idle screen brightness mode");
         pw.println("  undock");
@@ -171,17 +184,94 @@ class DisplayManagerShellCommand extends ShellCommand {
     }
 
     private int getDisplays() {
-        String category = getNextArg();
+        String opt = "", requestedType, category = null;
+        PrintWriter out = getOutPrintWriter();
+
+        List<Integer> displayTypeList = new ArrayList<>();
+        boolean showIdsOnly = false, filterByType = false;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "-i":
+                case "--ids-only":
+                    showIdsOnly = true;
+                    break;
+                case "-t":
+                case "--type":
+                    requestedType = getNextArgRequired();
+                    int displayType = getType(requestedType, out);
+                    if (displayType == -1) {
+                        return 1;
+                    }
+                    displayTypeList.add(displayType);
+                    filterByType = true;
+                    break;
+                case "-c":
+                case "--category":
+                    if (category != null) {
+                        out.println("Error: the category has been specified more than one time. "
+                                + "Please select only one category.");
+                        return 1;
+                    }
+                    category = getNextArgRequired();
+                    break;
+                case "":
+                    break;
+                default:
+                    out.println("Error: unknown option '" + opt + "'");
+                    return 1;
+            }
+        }
+
+        String lastCategoryArgument = getNextArg();
+        if (lastCategoryArgument != null) {
+            if (category != null) {
+                out.println("Error: the category has been specified both with the -c option and "
+                        + "the positional argument. Please select only one category.");
+                return 1;
+            }
+            category = lastCategoryArgument;
+        }
+
         DisplayManager dm = mService.getContext().getSystemService(DisplayManager.class);
         Display[] displays = dm.getDisplays(category);
-        PrintWriter out = getOutPrintWriter();
-        out.println("Displays:");
+
+        if (filterByType) {
+            displays = Arrays.stream(displays).filter(d -> displayTypeList.contains(d.getType()))
+                    .toArray(Display[]::new);
+        }
+
+        if (!showIdsOnly) {
+            out.println("Displays:");
+        }
         for (int i = 0; i < displays.length; i++) {
-            out.println("  " + displays[i]);
+            out.println((showIdsOnly ? displays[i].getDisplayId() : displays[i]));
         }
         return 0;
     }
 
+    private int getType(String type, PrintWriter out) {
+        type = type.toUpperCase(Locale.ENGLISH);
+        switch (type) {
+            case "UNKNOWN":
+                return TYPE_UNKNOWN;
+            case "INTERNAL":
+                return TYPE_INTERNAL;
+            case "EXTERNAL":
+                return TYPE_EXTERNAL;
+            case "WIFI":
+                return TYPE_WIFI;
+            case "OVERLAY":
+                return TYPE_OVERLAY;
+            case "VIRTUAL":
+                return TYPE_VIRTUAL;
+            default:
+                out.println("Error: argument for display type should be "
+                        + "one of 'UNKNOWN', 'INTERNAL', 'EXTERNAL', 'WIFI', 'OVERLAY', 'VIRTUAL', "
+                        + "but got '" + type + "' instead.");
+                return -1;
+        }
+    }
+
     private int showNotification() {
         final String notificationType = getNextArg();
         if (notificationType == null) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 7d9c0182a6913d67422d003862103c7cdfa3701c..0f0002781f93a9c10f24ebef0bcfce3454f48f1c 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 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
                         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 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
         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 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
 
         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 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
             // 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 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
                 // 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 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
                 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 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
                     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 68f72d3b085a361e82a91c24fbf23cf153e63d97..dfcda40d8e3c8167f99cdad158f2d222021beda3 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 @@ package com.android.server.display.brightness.clamper;
 import android.annotation.NonNull;
 import android.os.PowerManager;
 
+import com.android.server.display.DisplayBrightnessState;
+
 import java.io.PrintWriter;
 
 /**
@@ -33,6 +35,10 @@ abstract class BrightnessClamper<T> {
         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 787f786a97dadfd3b0eeab539497d893d46d543d..b57491949196f7eadcec501becda79ac36bcb4de 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 @@ import java.util.concurrent.Executor;
  */
 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 @@ public class BrightnessClamperController {
     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 @@ public class BrightnessClamperController {
         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 @@ public class BrightnessClamperController {
     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 @@ public class BrightnessClamperController {
         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/media/AudioAttributesUtils.java b/services/core/java/com/android/server/media/AudioAttributesUtils.java
index 5d5d59bcb6ef8cab8a046fbbb38ef03183319a73..8cb334dc2260d173373ee08e03507e18605ce707 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.AudioDeviceAttributes;
 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 @@ import android.media.MediaRoute2Info;
     @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 @@ import android.media.MediaRoute2Info;
         }
     }
 
-
     /* 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 33190ade4f42cfc2b0d685c91bb6ae9212d39165..360a6a721988c934232511fa793472b020a4188f 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.FEATURE_LOCAL_PLAYBACK;
 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 @@ import java.util.Objects;
     @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 @@ import java.util.Objects;
                 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 @@ import java.util.Objects;
             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/projection/FrameworkStatsLogWrapper.java b/services/core/java/com/android/server/media/projection/FrameworkStatsLogWrapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..5bad06777328bd6791c5443dbf49bcb809a29877
--- /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 8cbc368467bb96a6456ba282a08f88b72a1fed09..ce35a61143893e5a1fab2e110dc85ffac96307f2 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 @@ public final class MediaProjectionManagerService extends SystemService
         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 @@ public final class MediaProjectionManagerService extends SystemService
             return Looper.getMainLooper();
         }
 
-        MediaProjectionMetricsLogger mediaProjectionMetricsLogger() {
-            return MediaProjectionMetricsLogger.getInstance();
+        MediaProjectionMetricsLogger mediaProjectionMetricsLogger(Context context) {
+            return MediaProjectionMetricsLogger.getInstance(context);
         }
     }
 
@@ -293,6 +293,12 @@ public final class MediaProjectionManagerService extends SystemService
     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 @@ public final class MediaProjectionManagerService extends SystemService
                 .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()}.
@@ -841,6 +852,19 @@ public final class MediaProjectionManagerService extends SystemService
             }
         }
 
+        @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;
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 f18ecad09c42e64e4ba032cd384b57c4a9414ca9..c8b932ab6ccc748910670af425cb42591d0e4bbd 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 ff70cb35f9dcc207d098bac1838e35b77d3e94a9..244de0b3dd99105d4e359f6cb4cc09ce396cc494 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 @@ public class MediaProjectionSessionIdGenerator {
             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
index 4026d0c43484b7fe8eb1da5c29cc699a8df2567a..bfec58c8983db7034da395a7653d980b170c657d 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionTimestampStore.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionTimestampStore.java
@@ -59,8 +59,11 @@ public class MediaProjectionTimestampStore {
             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 MediaProjectionTimestampStore(preferences, InstantSource.system());
             }
             return sInstance;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b4d36db96c01312b7dd09d8e0a20d386f39f94fe..7ca56990f2d05b1e47eeacfbb3c5687b31ff1935 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_AWARE;
 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.annotations.VisibleForTesting;
 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 @@ public class NotificationManagerService extends SystemService {
         @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 class NotificationManagerService extends SystemService {
         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 @@ public class NotificationManagerService extends SystemService {
                         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 class NotificationManagerService extends SystemService {
         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 @@ public class NotificationManagerService extends SystemService {
                     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 @@ public class NotificationManagerService extends SystemService {
                 }
             }
         }
+
+        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 @@ public class NotificationManagerService extends SystemService {
             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 class NotificationManagerService extends SystemService {
 
         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 @@ public class NotificationManagerService extends SystemService {
 
         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 @@ public class NotificationManagerService extends SystemService {
         // 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 @@ public class NotificationManagerService extends SystemService {
         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 @@ public class NotificationManagerService extends SystemService {
             }
             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) {
@@ -6490,7 +6499,7 @@ public class NotificationManagerService extends SystemService {
                     pw.println("  mMaxPackageEnqueueRate=" + mMaxPackageEnqueueRate);
                     pw.println("  hideSilentStatusBar="
                             + mPreferencesHelper.shouldHideSilentStatusIcons());
-                    if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+                    if (Flags.refactorAttentionHelper()) {
                         mAttentionHelper.dump(pw, "    ", filter);
                     }
                 }
@@ -7756,7 +7765,7 @@ public class NotificationManagerService extends SystemService {
             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();
@@ -7889,7 +7898,7 @@ public class NotificationManagerService extends SystemService {
                     cancelGroupChildrenLocked(r, mCallingUid, mCallingPid, listenerName,
                             mSendDelete, childrenFlagChecker, mReason,
                             mCancellationElapsedTimeMs);
-                    if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+                    if (Flags.refactorAttentionHelper()) {
                         mAttentionHelper.updateLightsLocked();
                     } else {
                         updateLightsLocked();
@@ -8186,7 +8195,7 @@ public class NotificationManagerService extends SystemService {
 
                     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()),
@@ -9173,7 +9182,7 @@ public class NotificationManagerService extends SystemService {
                     || 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));
@@ -9553,7 +9562,7 @@ public class NotificationManagerService extends SystemService {
                 });
             }
 
-            if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+            if (Flags.refactorAttentionHelper()) {
                 mAttentionHelper.clearEffectsLocked(canceledKey);
             } else {
                 // sound
@@ -9917,7 +9926,7 @@ public class NotificationManagerService extends SystemService {
                             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/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 3a6664a72439185899c6040558caf74af4016f3b..7c0fc998abcfff7fd42e19b922af8a145165ba5a 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.DeviceIdleManager;
 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 @@ public class PhoneWindowManager implements WindowManagerPolicy {
     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 @@ public class PhoneWindowManager implements WindowManagerPolicy {
     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 @@ public class PhoneWindowManager implements WindowManagerPolicy {
             return mWindowManagerFuncs;
         }
 
+        Looper getLooper() {
+            return mLooper;
+        }
+
         AccessibilityShortcutController getAccessibilityShortcutController(
                 Context context, Handler handler, int initialUserId) {
             return new AccessibilityShortcutController(context, handler, initialUserId);
@@ -2208,7 +2220,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
     /** {@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 @@ public class PhoneWindowManager implements WindowManagerPolicy {
                     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/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index c54e3bdf0d516e7af95c79b1828d6c196fff91ea..5f8bbe5f18ad353e3e6be274180ed82a5ee01a8e 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 static com.android.server.wallpaper.WallpaperUtils.makeWallpaperIdLocked;
 
 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.pm.PackageManager;
 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 @@ class WallpaperDataParser {
     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 @@ class WallpaperDataParser {
         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 @@ class WallpaperDataParser {
     }
 
     /**
-     * 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 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;
-        }
+        boolean loadSystem = (which & FLAG_SYSTEM) != 0;
+        boolean loadLock = (which & FLAG_LOCK) != 0;
+        WallpaperData wallpaper = null;
+        WallpaperData 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 @@ class WallpaperDataParser {
                 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 @@ class WallpaperDataParser {
                             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 c7a3c4349f4cfa97f7a14eb7c05aa317e79e3048..bdcde66a8102b6315c3625d64dddcc5de03fd562 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -188,8 +188,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
     }
 
     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. */
@@ -230,7 +228,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
             mWallpaperLockFile = new File(mWallpaperDir, WALLPAPER_LOCK_ORIG);
         }
 
-        WallpaperData dataForEvent(boolean sysChanged, boolean lockChanged) {
+        WallpaperData dataForEvent(boolean lockChanged) {
             WallpaperData wallpaper = null;
             synchronized (mLock) {
                 if (lockChanged) {
@@ -252,7 +250,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
             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);
@@ -378,7 +376,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
                 }
 
                 saveSettingsLocked(wallpaper.userId);
-                if ((sysWallpaperChanged || lockWallpaperChanged) && localSync != null) {
+                if (localSync != null) {
                     localSync.complete();
                 }
             }
@@ -389,129 +387,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
             }
         }
 
-        // 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);
         }
     }
 
@@ -528,17 +406,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
         }
     }
 
-    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");
@@ -597,14 +464,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
 
     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();
@@ -633,15 +498,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
                 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);
-            }
-        }
     }
 
     /**
@@ -762,8 +618,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
     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() {
 
@@ -814,8 +668,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
     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.
@@ -1017,8 +869,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
                 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);
                 }
             }
         };
@@ -1198,7 +1050,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
                 } 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(),
@@ -1238,7 +1090,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
                                 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;
                                 }
@@ -1257,7 +1109,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
                                     && 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();
@@ -1294,19 +1146,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
                 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);
@@ -1492,9 +1333,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
                                 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);
                         }
                     }
                 }
@@ -1568,7 +1407,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
 
         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());
@@ -1578,7 +1416,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
                     if (doit) {
                         Slog.w(TAG, "Wallpaper uninstalled, removing: "
                                 + wallpaper.wallpaperComponent);
-                        clearWallpaperLocked(which, wallpaper.userId, null);
+                        clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, null);
                     }
                 }
             }
@@ -1599,7 +1437,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
                 } 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
@@ -1686,9 +1524,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
         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());
@@ -1755,8 +1590,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
                 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) {
@@ -1883,29 +1717,19 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
     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
@@ -1954,21 +1778,15 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
                 }
                 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);
@@ -1988,11 +1806,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
 
     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;
@@ -2006,37 +1821,19 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
                 } 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");
@@ -2067,12 +1864,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
 
         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);
@@ -2153,91 +1946,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
         }
     }
 
-    // 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);
@@ -2615,45 +2323,20 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
      * @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) {
@@ -2664,11 +2347,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
 
     private void pauseOrResumeRenderingImmediately(boolean pause) {
         synchronized (mLock) {
-            final WallpaperData[] wallpapers = mIsLockscreenLiveWallpaperEnabled
-                    ? getActiveWallpapers() : new WallpaperData[] {
-                            mWallpaperMap.get(mCurrentUserId) };
-            for (WallpaperData data : wallpapers) {
-                if (data.connection == null || data.connection.mInfo == null) {
+            for (WallpaperData data : getActiveWallpapers()) {
+                if (data.connection.mInfo == null) {
                     continue;
                 }
                 if (pause || LocalServices.getService(ActivityTaskManagerInternal.class)
@@ -2697,34 +2377,17 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
     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);
-                                }
-                            }
-                        });
+                    }
+                });
             }
         }
     }
@@ -2735,36 +2398,18 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
     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);
-                                }
-                            }
-                        });
+                    }
+                });
             }
         }
     }
@@ -2774,35 +2419,18 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
      */
     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);
-                    }
-                }
             }
         }
     }
@@ -2812,35 +2440,18 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
      */
     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);
-                    }
-                }
             }
         }
     }
@@ -2850,25 +2461,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
      */
     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 {
@@ -2884,15 +2477,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
         }
     }
 
-    @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);
@@ -2904,12 +2488,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
                 : 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}
@@ -3043,54 +2626,29 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
                     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);
@@ -3246,10 +2804,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
             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);
@@ -3277,13 +2832,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
         }
     }
 
-    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
@@ -3297,25 +2852,21 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
 
         // 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;
         }
     }
 
@@ -3372,13 +2923,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
     @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,
@@ -3491,87 +3037,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
         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.
@@ -3743,21 +3208,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
                 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;
@@ -3773,7 +3228,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
     }
 
     // Updates tracking of the currently bound wallpapers.
-    // Assumes isLockscreenLiveWallpaperEnabled is true.
     private void updateCurrentWallpapers(WallpaperData newWallpaper) {
         if (newWallpaper.userId != mCurrentUserId || newWallpaper.equals(mFallbackWallpaper)) {
             return;
@@ -3787,8 +3241,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
         }
     }
 
-    // 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;
@@ -3980,11 +3433,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
         return (wallpaper != null) ? wallpaper.allowBackup : false;
     }
 
-    @Override
-    public boolean isLockscreenLiveWallpaperEnabled() {
-        return mIsLockscreenLiveWallpaperEnabled;
-    }
-
     @Override
     public boolean isMultiCropEnabled() {
         return mIsMultiCropEnabled;
@@ -4074,13 +3522,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
 
     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) {
@@ -4243,8 +3690,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
             if (mFallbackWallpaper != null) {
                 dumpWallpaper(mFallbackWallpaper, pw);
             }
-            pw.print("mIsLockscreenLiveWallpaperEnabled=");
-            pw.println(mIsLockscreenLiveWallpaperEnabled);
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index 06448d0c1e84962c531689246ee9effab23b1847..022ef61529291fc28de0169d9d46e0b60c2b60a9 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.NonNull;
 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,16 +37,28 @@ import android.os.ServiceManager;
 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}.
  */
 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.
      */
@@ -87,15 +100,20 @@ final class ContentRecorder implements WindowContainerListener {
     @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 @@ final class ContentRecorder implements WindowContainerListener {
         }
     }
 
+    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 @@ final class ContentRecorder implements WindowContainerListener {
      */
     @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 @@ final class ContentRecorder implements WindowContainerListener {
         }
 
         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 @@ final class ContentRecorder implements WindowContainerListener {
                         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/SnapshotController.java b/services/core/java/com/android/server/wm/SnapshotController.java
index 2e7ff7a6b9b850f53709cd4e85233a1ec3e603d3..f68e39207b21f95883ecd4ab39b5120705815bda 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 @@ class SnapshotController {
             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/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 94e66ffd8373d5ee51e824516f0d0d485dabb9bc..33ef3c5629e307c1466a2f5df3e563acac0361d1 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.Debug;
 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 @@ class WallpaperController {
 
     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 @@ class WallpaperController {
     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);
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 1ed14310a500e8bcef727347707c057632577930..15bd6078dc2d1fd4a0d443c72ec8d9beb1bad38b 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 @@ class WallpaperWindowToken extends WindowToken {
             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;
-
-            // 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 */);
-        }
+        // 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 */);
     }
 
     boolean canShowWhenLocked() {
diff --git a/services/midi/Android.bp b/services/midi/Android.bp
index 4b5f8a7bf0acd77c93dc5f7af50fc4fdfd3d2409..a385fe38495b4ff92da27d5c4a999b3114cb8b0e 100644
--- a/services/midi/Android.bp
+++ b/services/midi/Android.bp
@@ -18,8 +18,8 @@ java_library_static {
     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/PackageManagerServiceTests/host/Android.bp b/services/tests/PackageManagerServiceTests/host/Android.bp
index 6eacef767042156486cb599ea050b97114c20e77..c617ec49ab32b3aa186bbd28fbd98f9e9ca91469 100644
--- a/services/tests/PackageManagerServiceTests/host/Android.bp
+++ b/services/tests/PackageManagerServiceTests/host/Android.bp
@@ -58,6 +58,7 @@ java_test_host {
         ":PackageManagerTestOverlayTarget",
         ":PackageManagerTestOverlayTargetNoOverlayable",
         ":PackageManagerTestAppDeclaresStaticLibrary",
+        ":PackageManagerTestAppDifferentPkgName",
         ":PackageManagerTestAppStub",
         ":PackageManagerTestAppUsesStaticLibrary",
         ":PackageManagerTestAppVersion1",
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/TamperedUpdatedSystemPackageTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/TamperedUpdatedSystemPackageTest.kt
index c4906041ea5de10c78202c9bf9e782c642e68bd1..304f605d5b95abdcb9b381254a155dfa00d47fe0 100644
--- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/TamperedUpdatedSystemPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/TamperedUpdatedSystemPackageTest.kt
@@ -44,6 +44,10 @@ class TamperedUpdatedSystemPackageTest : BaseHostJUnit4Test() {
         private const val VERSION_TWO_ALT_KEY = "PackageManagerTestAppVersion2AltKey.apk"
         private const val VERSION_TWO_ALT_KEY_IDSIG =
                 "PackageManagerTestAppVersion2AltKey.apk.idsig"
+
+        private const val ANOTHER_PKG_NAME = "com.android.server.pm.test.test_app2"
+        private const val ANOTHER_PKG = "PackageManagerTestAppDifferentPkgName.apk"
+
         private const val STRICT_SIGNATURE_CONFIG_PATH =
                 "/system/etc/sysconfig/preinstalled-packages-strict-signature.xml"
         private const val TIMESTAMP_REFERENCE_FILE_PATH = "/data/local/tmp/timestamp.ref"
@@ -74,6 +78,7 @@ class TamperedUpdatedSystemPackageTest : BaseHostJUnit4Test() {
     @After
     fun removeApk() {
         device.uninstallPackage(TEST_PKG_NAME)
+        device.uninstallPackage(ANOTHER_PKG_NAME)
     }
 
     @Before
@@ -90,7 +95,9 @@ class TamperedUpdatedSystemPackageTest : BaseHostJUnit4Test() {
                     .readText()
                     .replace(
                         "</config>",
-                            "<require-strict-signature package=\"${TEST_PKG_NAME}\"/></config>"
+                            "<require-strict-signature package=\"${TEST_PKG_NAME}\"/>" +
+                            "<require-strict-signature package=\"${ANOTHER_PKG_NAME}\"/>" +
+                            "</config>"
                     )
             writeText(newConfigText)
         }
@@ -146,10 +153,7 @@ class TamperedUpdatedSystemPackageTest : BaseHostJUnit4Test() {
                 tempFolder.newFile()
         )
         assertThat(device.installPackage(versionTwoFile, true)).isNull()
-        val baseApkPath = device.executeShellCommand("pm path ${TEST_PKG_NAME}")
-                .lineSequence()
-                .first()
-                .replace("package:", "")
+        val baseApkPath = getBaseApkPath(TEST_PKG_NAME)
         assertThat(baseApkPath).doesNotContain(productPath.toString())
         preparer.pushResourceFile(VERSION_TWO_ALT_KEY_IDSIG, baseApkPath.toString() + ".idsig")
 
@@ -175,4 +179,23 @@ class TamperedUpdatedSystemPackageTest : BaseHostJUnit4Test() {
         assertThat(device.executeShellCommand("pm path ${TEST_PKG_NAME}"))
                 .contains(productPath.toString())
     }
+
+    @Test
+    fun allowlistedPackageIsNotASystemApp() {
+        // If an allowlisted package isn't a system app, make sure install and boot still works
+        // normally.
+        assertThat(device.installJavaResourceApk(tempFolder, ANOTHER_PKG, /* reinstall */ false))
+                .isNull()
+        assertThat(getBaseApkPath(ANOTHER_PKG_NAME)).startsWith("/data/app/")
+
+        preparer.reboot()
+        assertThat(getBaseApkPath(ANOTHER_PKG_NAME)).startsWith("/data/app/")
+    }
+
+    private fun getBaseApkPath(pkgName: String): String {
+        return device.executeShellCommand("pm path $pkgName")
+                .lineSequence()
+                .first()
+                .replace("package:", "")
+    }
 }
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp
index bee7c4019fc16ffcb42cf2695736bf9b283f0e51..b826590b7440220be9b2c8982fa6b9a7b5dcdaec 100644
--- a/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp
@@ -76,3 +76,11 @@ android_test_helper_app {
     certificate: ":FrameworksServicesTests_keyset_A_cert",
     v4_signature: true,
 }
+
+android_test_helper_app {
+    name: "PackageManagerTestAppDifferentPkgName",
+    manifest: "AndroidManifestDifferentPkgName.xml",
+    srcs: [
+        "src/**/*.kt",
+    ],
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Generic/AndroidManifestDifferentPkgName.xml b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/AndroidManifestDifferentPkgName.xml
new file mode 100644
index 0000000000000000000000000000000000000000..0c5d36e389726956e6f9881d0fe88093c02b5258
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/AndroidManifestDifferentPkgName.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 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.
+  -->
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.pm.test.test_app2"
+    android:versionCode="1"
+    >
+
+    <permission
+        android:name="com.android.server.pm.test.test_app.TEST_PERMISSION"
+        android:protectionLevel="normal"
+        />
+
+    <application>
+        <activity android:name="com.android.server.pm.test.test_app.TestActivity"
+            android:label="PackageManagerTestApp" />
+    </application>
+
+</manifest>
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 c12aedb72350c12126cc19dca58c7f30edadf5a9..a400f1243afb1e7cd62d7ae900cf3f6eb8862482 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 @@ public class DisplayBrightnessStateTest {
                 .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/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
index 8b54d6d22c2b9744d16d91c8f1c2cca89c8c8422..47521d13e49c6804f2a03a335d9b6c54650ac827 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.LocalServices;
 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;
@@ -1313,6 +1314,54 @@ public final class DisplayPowerController2Test {
                 eq(transitionRate), eq(true));
     }
 
+    @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() {
@@ -1637,13 +1686,20 @@ public final class DisplayPowerController2Test {
                 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 @@ public final class DisplayPowerController2Test {
 
         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 class DisplayPowerController2Test {
         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 @@ public final class DisplayPowerController2Test {
                 ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
                 HighBrightnessModeController hbmController,
                 HdrClamper hdrClamper,
+                BrightnessClamperController clamperController,
                 HighBrightnessModeMetadata hbmMetadata,
                 BrightnessMappingStrategy brightnessMappingStrategy,
                 DisplayPowerController2.Injector injector,
@@ -1709,6 +1767,7 @@ public final class DisplayPowerController2Test {
             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 @@ public final class DisplayPowerController2Test {
 
         private final HdrClamper mHdrClamper;
 
+        private final BrightnessClamperController mClamperController;
+
         private final DisplayManagerFlags mFlags;
 
         TestInjector(DisplayPowerState dps, DualRampAnimator<DisplayPowerState> animator,
@@ -1738,6 +1799,7 @@ public final class DisplayPowerController2Test {
                 ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
                 HighBrightnessModeController highBrightnessModeController,
                 HdrClamper hdrClamper,
+                BrightnessClamperController clamperController,
                 DisplayManagerFlags flags) {
             mDisplayPowerState = dps;
             mAnimator = animator;
@@ -1748,6 +1810,7 @@ public final class DisplayPowerController2Test {
             mScreenOffBrightnessSensorController = screenOffBrightnessSensorController;
             mHighBrightnessModeController = highBrightnessModeController;
             mHdrClamper = hdrClamper;
+            mClamperController = clamperController;
             mFlags = flags;
         }
 
@@ -1863,6 +1926,14 @@ public final class DisplayPowerController2Test {
                     displayDeviceConfig, mHdrClamper, mFlags, displayToken, info);
         }
 
+        @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) {
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 c0e0df98213f9fae49cb1ca2f67ec5fc321c1c95..ff2b1f466a5c9dc8f0e9e65b1f28c0383cc56927 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 @@ public class BrightnessClamperControllerTest {
         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 @@ public class BrightnessClamperControllerTest {
         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 @@ public class BrightnessClamperControllerTest {
         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 @@ public class BrightnessClamperControllerTest {
         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 @@ public class BrightnessClamperControllerTest {
         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 @@ public class BrightnessClamperControllerTest {
         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 @@ public class BrightnessClamperControllerTest {
         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 @@ public class BrightnessClamperControllerTest {
         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/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index eefe5af314a679f08cbf5c5bc86bd4da97820651..3dbab1360af42896e2e45453126c437e0571a296 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 @@ public class WallpaperManagerServiceTests {
         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/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index 6bfd93b66f6179dc1c8d2ea4eaa0554db27e6bc3..4bb7d63995acd5d5458d8a74e5bdfeb4d409d265 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_IMS;
 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.Test;
 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 @@ public class JobStoreTest {
         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 @@ public class JobStoreTest {
         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/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index f94aff706a67015e9225588d6587d78813a85f6e..9f6d7f223716ad3fe2c7fb64ccf63e8fc5b60d2a 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.DEFAULT_DISPLAY;
 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.never;
 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 @@ public class MediaProjectionManagerServiceTest {
     private final MediaProjectionManagerService.Injector mMediaProjectionMetricsLoggerInjector =
             new MediaProjectionManagerService.Injector() {
                 @Override
-                MediaProjectionMetricsLogger mediaProjectionMetricsLogger() {
+                MediaProjectionMetricsLogger mediaProjectionMetricsLogger(Context context) {
                     return mMediaProjectionMetricsLogger;
                 }
             };
@@ -310,6 +312,70 @@ public class MediaProjectionManagerServiceTest {
         assertThat(secondProjection).isNotEqualTo(projection);
     }
 
+    @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,
@@ -586,6 +652,18 @@ public class MediaProjectionManagerServiceTest {
                 /* 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 class MediaProjectionManagerServiceTest {
     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 0000000000000000000000000000000000000000..73b4cc8cc59298b4fab88598118de2156ab9a9c7
--- /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/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index 9bd938f2e0a709c4e1b29325b2df414b9b9763aa..cf8548cfe68903f527f782467f67fc00cc84ee31 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.AccessibilityEvent;
 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 com.android.server.pm.PackageManagerService;
 
 import java.util.List;
 import java.util.Objects;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -190,7 +193,7 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
         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 75d012a8e1f2c1b5c853f8ce844087c1d5735575..6792cfe6e78897dc18ffa31c3a4c02e8ac97bc8c 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.UserHandle.USER_SYSTEM;
 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.DEFAULT_DISPLAY;
 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.UserHandle;
 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
     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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
     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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
                 });
 
         // 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
         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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
         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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
     @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 {
@@ -9146,7 +9155,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
     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 {
@@ -12751,6 +12760,23 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
         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 121e29609df5ccf8ddd0d383fe025e75e201e2fc..337dd22e87120145ca251e16f7a77d5e5ddd1d38 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 @@ public class NotificationVisitUrisTest extends UiServiceTestCase {
     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/wmtests/Android.bp b/services/tests/wmtests/Android.bp
index af39b2f027eecdfd2de9bff6df1c5262ced56643..1b8d746f271fa8ebe223fd0d15a435f9d4da3db0 100644
--- a/services/tests/wmtests/Android.bp
+++ b/services/tests/wmtests/Android.bp
@@ -60,6 +60,7 @@ android_test {
         "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 8db09f9e3a16119318fdc32169084000c666ed52..61c4d06131e16bf489dcddce85ac6a8430ccf0d9 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 com.android.server.policy.WindowManagerPolicy.ACTION_PASS_TO_USER;
 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 @@ class ShortcutKeyTestBase {
      *      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 2244dbe8af98357be24a95253acb68f03e591803..261d3cc3c8d9606ca80161fbad82753964c525de 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.RemoteException;
 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 @@ class TestPhoneWindowManager {
     @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 @@ class TestPhoneWindowManager {
 
     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 @@ class TestPhoneWindowManager {
     }
 
     void tearDown() {
-        mHandlerThread.quitSafely();
         LocalServices.removeServiceForTest(InputMethodManagerInternal.class);
         Mockito.reset(mPhoneWindowManager);
         mMockitoSession.finishMocking();
@@ -327,10 +326,6 @@ class TestPhoneWindowManager {
         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 @@ class TestPhoneWindowManager {
      * 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 @@ class TestPhoneWindowManager {
     }
 
     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 @@ class TestPhoneWindowManager {
     }
 
     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 @@ class TestPhoneWindowManager {
     }
 
     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 @@ class TestPhoneWindowManager {
     }
 
     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 @@ class TestPhoneWindowManager {
     }
 
     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 @@ class TestPhoneWindowManager {
     }
 
     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 @@ class TestPhoneWindowManager {
 
     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 d2eb1cc0222b7556a9e53053632b01f952dc8ea9..78566fb06179fe2908b29e0166c5494fc64114e7 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 @@ public class ContentRecorderTests extends WindowTestsBase {
     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(),
+        doReturn(INVALID_DISPLAY).when(mWm.mDisplayManagerInternal).getDisplayIdToMirror(anyInt());
+
+        // 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());
-        mRecordedSurface = surfaceControlMirrors(sSurfaceSize);
+    }
 
-        doReturn(INVALID_DISPLAY).when(mWm.mDisplayManagerInternal).getDisplayIdToMirror(anyInt());
+    private DisplayInfo createDisplayInfo(int width, int height) {
+        // GIVEN SurfaceControl can successfully mirror the provided surface.
+        mSurfaceSize = new Point(width, height);
+        mRecordedSurface = surfaceControlMirrors(mSurfaceSize);
 
-        // GIVEN the VirtualDisplay associated with the session (so the display has state ON).
         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 @@ public class ContentRecorderTests extends WindowTestsBase {
         // 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 @@ public class ContentRecorderTests extends WindowTestsBase {
 
     @Test
     public void testUpdateRecording_display() {
+        defaultInit();
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
@@ -147,6 +163,7 @@ public class ContentRecorderTests extends WindowTestsBase {
 
     @Test
     public void testUpdateRecording_display_invalidDisplayIdToMirror() {
+        defaultInit();
         ContentRecordingSession session = ContentRecordingSession.createDisplaySession(
                 INVALID_DISPLAY);
         mContentRecorder.setContentRecordingSession(session);
@@ -156,6 +173,7 @@ public class ContentRecorderTests extends WindowTestsBase {
 
     @Test
     public void testUpdateRecording_display_noDisplayContentToMirror() {
+        defaultInit();
         doReturn(null).when(
                 mWm.mRoot).getDisplayContent(anyInt());
         mContentRecorder.setContentRecordingSession(mDisplaySession);
@@ -165,6 +183,7 @@ public class ContentRecorderTests extends WindowTestsBase {
 
     @Test
     public void testUpdateRecording_task_nullToken() {
+        defaultInit();
         ContentRecordingSession session = mTaskSession;
         session.setVirtualDisplayId(mDisplaySession.getVirtualDisplayId());
         session.setTokenToRecord(null);
@@ -176,6 +195,7 @@ public class ContentRecorderTests extends WindowTestsBase {
 
     @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 @@ public class ContentRecorderTests extends WindowTestsBase {
 
     @Test
     public void testUpdateRecording_wasPaused() {
+        defaultInit();
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
 
@@ -197,6 +218,7 @@ public class ContentRecorderTests extends WindowTestsBase {
 
     @Test
     public void testUpdateRecording_waitingForConsent() {
+        defaultInit();
         mContentRecorder.setContentRecordingSession(mWaitingDisplaySession);
         mContentRecorder.updateRecording();
         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
@@ -209,6 +231,7 @@ public class ContentRecorderTests extends WindowTestsBase {
 
     @Test
     public void testOnConfigurationChanged_neverRecording() {
+        defaultInit();
         mContentRecorder.onConfigurationChanged(ORIENTATION_PORTRAIT);
 
         verify(mTransaction, never()).setPosition(eq(mRecordedSurface), anyFloat(), anyFloat());
@@ -218,6 +241,7 @@ public class ContentRecorderTests extends WindowTestsBase {
 
     @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 @@ public class ContentRecorderTests extends WindowTestsBase {
 
     @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 @@ public class ContentRecorderTests extends WindowTestsBase {
 
     @Test
     public void testOnConfigurationChanged_rotateVirtualDisplay() {
+        defaultInit();
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
 
@@ -271,12 +297,13 @@ public class ContentRecorderTests extends WindowTestsBase {
      */
     @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 @@ public class ContentRecorderTests extends WindowTestsBase {
 
     @Test
     public void testOnTaskOrientationConfigurationChanged_resizesSurface() {
+        defaultInit();
         mContentRecorder.setContentRecordingSession(mTaskSession);
         mContentRecorder.updateRecording();
 
@@ -314,6 +342,7 @@ public class ContentRecorderTests extends WindowTestsBase {
 
     @Test
     public void testOnTaskBoundsConfigurationChanged_notifiesCallback() {
+        defaultInit();
         mTask.getRootTask().setWindowingMode(WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
 
         final int minWidth = 222;
@@ -351,6 +380,7 @@ public class ContentRecorderTests extends WindowTestsBase {
 
     @Test
     public void testTaskWindowingModeChanged_pip_stopsRecording() {
+        defaultInit();
         // WHEN a recording is ongoing.
         mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         mContentRecorder.setContentRecordingSession(mTaskSession);
@@ -368,6 +398,7 @@ public class ContentRecorderTests extends WindowTestsBase {
 
     @Test
     public void testTaskWindowingModeChanged_fullscreen_startsRecording() {
+        defaultInit();
         // WHEN a recording is ongoing.
         mTask.setWindowingMode(WINDOWING_MODE_PINNED);
         mContentRecorder.setContentRecordingSession(mTaskSession);
@@ -384,6 +415,7 @@ public class ContentRecorderTests extends WindowTestsBase {
 
     @Test
     public void testStartRecording_notifiesCallback_taskSession() {
+        defaultInit();
         // WHEN a recording is ongoing.
         mContentRecorder.setContentRecordingSession(mTaskSession);
         mContentRecorder.updateRecording();
@@ -396,6 +428,7 @@ public class ContentRecorderTests extends WindowTestsBase {
 
     @Test
     public void testStartRecording_notifiesCallback_displaySession() {
+        defaultInit();
         // WHEN a recording is ongoing.
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
@@ -408,6 +441,7 @@ public class ContentRecorderTests extends WindowTestsBase {
 
     @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 @@ public class ContentRecorderTests extends WindowTestsBase {
 
     @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 @@ public class ContentRecorderTests extends WindowTestsBase {
 
     @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 @@ public class ContentRecorderTests extends WindowTestsBase {
 
     @Test
     public void testOnVisibleRequestedChanged_notifiesCallback() {
+        defaultInit();
         // WHEN a recording is ongoing.
         mContentRecorder.setContentRecordingSession(mTaskSession);
         mContentRecorder.updateRecording();
@@ -471,6 +508,7 @@ public class ContentRecorderTests extends WindowTestsBase {
 
     @Test
     public void testOnVisibleRequestedChanged_noRecording_doesNotNotifyCallback() {
+        defaultInit();
         // WHEN a recording is not ongoing.
         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
 
@@ -493,6 +531,7 @@ public class ContentRecorderTests extends WindowTestsBase {
 
     @Test
     public void testPauseRecording_pausesRecording() {
+        defaultInit();
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
 
@@ -502,12 +541,14 @@ public class ContentRecorderTests extends WindowTestsBase {
 
     @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 @@ public class ContentRecorderTests extends WindowTestsBase {
 
     @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 @@ public class ContentRecorderTests extends WindowTestsBase {
 
     @Test
     public void testRemoveTask_stopsRecording_nullSessionShouldNotThrowExceptions() {
+        defaultInit();
         mContentRecorder.setContentRecordingSession(mTaskSession);
         mContentRecorder.updateRecording();
         mContentRecorder.setContentRecordingSession(null);
@@ -541,6 +585,7 @@ public class ContentRecorderTests extends WindowTestsBase {
 
     @Test
     public void testUpdateMirroredSurface_capturedAreaResized() {
+        defaultInit();
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
@@ -548,9 +593,9 @@ public class ContentRecorderTests extends WindowTestsBase {
         // 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,15 +603,139 @@ public class ContentRecorderTests extends WindowTestsBase {
                 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(
                 displayAreaBounds.width(), displayAreaBounds.height());
     }
 
+    @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 @@ public class ContentRecorderTests extends WindowTestsBase {
 
     @Test
     public void testDisplayContentUpdatesRecording_withSurface() {
+        defaultInit();
         // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
         // mirror.
         setUpDefaultTaskDisplayAreaWindowToken();
@@ -602,12 +772,13 @@ public class ContentRecorderTests extends WindowTestsBase {
 
     @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/SystemServiceTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java
index 486486869d05580916d43ff412e92b74a89acbd5..3cb4a1d7b7ec7d302960dd3405b40ddeffea0379 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 @@ package com.android.server.wm;
 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 @@ import java.util.concurrent.Callable;
 
 /** 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 =
@@ -64,6 +70,11 @@ class SystemServiceTestsBase {
         return mLockRule.waitForLocked(callable);
     }
 
+    /**
+     * 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 7634d9f601d4da1526662f0564eef03dd4b7b4fe..2597465337ecff3535f838e02371c441fa9c86e2 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 @@ public class SystemServicesTestRule implements TestRule {
     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 @@ public class SystemServicesTestRule implements TestRule {
     }
 
     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/usage/java/com/android/server/usage/UsageStatsHandlerThread.java b/services/usage/java/com/android/server/usage/UsageStatsHandlerThread.java
new file mode 100644
index 0000000000000000000000000000000000000000..31418d65b8b6ebc6e91bb1a91ddaba80b4f961bb
--- /dev/null
+++ b/services/usage/java/com/android/server/usage/UsageStatsHandlerThread.java
@@ -0,0 +1,64 @@
+/*
+ * 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.usage;
+
+import android.os.Looper;
+import android.os.Trace;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.ServiceThread;
+
+/**
+ * Shared singleton default priority thread for usage stats message handling.
+ */
+public class UsageStatsHandlerThread extends ServiceThread {
+    private static final long SLOW_DISPATCH_THRESHOLD_MS = 10_000;
+    private static final long SLOW_DELIVERY_THRESHOLD_MS = 30_000;
+
+    private static final Object sLock = new Object();
+
+    @GuardedBy("sLock")
+    private static UsageStatsHandlerThread sInstance;
+
+    private UsageStatsHandlerThread() {
+        super("android.usagestats", android.os.Process.THREAD_PRIORITY_DEFAULT,
+                /* allowIo= */ true);
+    }
+
+    @GuardedBy("sLock")
+    private static void ensureThreadLocked() {
+        if (sInstance != null) {
+            return;
+        }
+
+        sInstance = new UsageStatsHandlerThread();
+        sInstance.start();
+        final Looper looper = sInstance.getLooper();
+        looper.setTraceTag(Trace.TRACE_TAG_SYSTEM_SERVER);
+        looper.setSlowLogThresholdMs(
+                SLOW_DISPATCH_THRESHOLD_MS, SLOW_DELIVERY_THRESHOLD_MS);
+    }
+
+    /**
+     * Obtain a singleton instance of the UsageStatsHandlerThread.
+     */
+    public static UsageStatsHandlerThread get() {
+        synchronized (sLock) {
+            ensureThreadLocked();
+            return sInstance;
+        }
+    }
+}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 55b5d11d938aef4f91eeafabea9de9791fbc4235..e413663160b59eaf631478a00735ad93e74e82a8 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -202,6 +202,7 @@ public class UsageStatsService extends SystemService implements
     static final int MSG_HANDLE_LAUNCH_TIME_ON_USER_UNLOCK = 8;
     static final int MSG_NOTIFY_ESTIMATED_LAUNCH_TIMES_CHANGED = 9;
     static final int MSG_UID_REMOVED = 10;
+    static final int MSG_USER_STARTED = 11;
 
     private final Object mLock = new Object();
     private Handler mHandler;
@@ -334,7 +335,7 @@ public class UsageStatsService extends SystemService implements
         mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
         mPackageManager = getContext().getPackageManager();
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
-        mHandler = new H(BackgroundThread.get().getLooper());
+        mHandler = getUsageEventProcessingHandler();
         mIoHandler = new Handler(IoThread.get().getLooper(), mIoHandlerCallback);
 
         mAppStandby = mInjector.getAppStandbyController(getContext());
@@ -380,10 +381,12 @@ public class UsageStatsService extends SystemService implements
 
         IntentFilter filter = new IntentFilter(Intent.ACTION_USER_REMOVED);
         filter.addAction(Intent.ACTION_USER_STARTED);
-        getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL,
-                filter, null, /* Handler scheduler */ null);
+        getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL, filter,
+                null, /* scheduler= */ Flags.useDedicatedHandlerThread() ? mHandler : null);
+
         getContext().registerReceiverAsUser(new UidRemovedReceiver(), UserHandle.ALL,
-                new IntentFilter(ACTION_UID_REMOVED), null, /* Handler scheduler */ null);
+                new IntentFilter(ACTION_UID_REMOVED), null,
+                /* scheduler= */ Flags.useDedicatedHandlerThread() ? mHandler : null);
 
         mRealTimeSnapshot = SystemClock.elapsedRealtime();
         mSystemTimeSnapshot = System.currentTimeMillis();
@@ -471,6 +474,14 @@ public class UsageStatsService extends SystemService implements
         }
     }
 
+    private Handler getUsageEventProcessingHandler() {
+        if (Flags.useDedicatedHandlerThread()) {
+            return new H(UsageStatsHandlerThread.get().getLooper());
+        } else {
+            return new H(BackgroundThread.get().getLooper());
+        }
+    }
+
     private void onUserUnlocked(int userId) {
         // fetch the installed packages outside the lock so it doesn't block package manager.
         final HashMap<String, Long> installedPackages = getInstalledPackages(userId);
@@ -618,7 +629,7 @@ public class UsageStatsService extends SystemService implements
                 }
             } else if (Intent.ACTION_USER_STARTED.equals(action)) {
                 if (userId >= 0) {
-                    mAppStandby.postCheckIdleStates(userId);
+                    mHandler.obtainMessage(MSG_USER_STARTED, userId, 0).sendToTarget();
                 }
             }
         }
@@ -1554,8 +1565,7 @@ public class UsageStatsService extends SystemService implements
         synchronized (mLaunchTimeAlarmQueues) {
             LaunchTimeAlarmQueue alarmQueue = mLaunchTimeAlarmQueues.get(userId);
             if (alarmQueue == null) {
-                alarmQueue = new LaunchTimeAlarmQueue(
-                    userId, getContext(), BackgroundThread.get().getLooper());
+                alarmQueue = new LaunchTimeAlarmQueue(userId, getContext(), mHandler.getLooper());
                 mLaunchTimeAlarmQueues.put(userId, alarmQueue);
             }
 
@@ -2040,6 +2050,9 @@ public class UsageStatsService extends SystemService implements
                 case MSG_UID_REMOVED:
                     mResponseStatsTracker.onUidRemoved(msg.arg1);
                     break;
+                case MSG_USER_STARTED:
+                    mAppStandby.postCheckIdleStates(msg.arg1);
+                    break;
                 case MSG_PACKAGE_REMOVED:
                     onPackageRemoved(msg.arg1, (String) msg.obj);
                     break;
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 7a0bf9038f7c5e655ae94473c5c9774137a17ab3..540cecfbe7c931ef822ad976135b1326be075607 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1055,6 +1055,14 @@ public class CarrierConfigManager {
     public static final String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL =
             "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
@@ -9874,6 +9882,8 @@ public class CarrierConfigManager {
         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 e9af486219f7372cb2e46205af92fee3d517f53f..11cbcb1c149dcc0a9cdfde997dc65bb5832e3952 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.text.TextUtils;
 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 class ApnSetting implements Parcelable {
     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 @@ public class ApnSetting implements Parcelable {
             TYPE_BIP,
             TYPE_VSIM,
             TYPE_ENTERPRISE,
+            TYPE_RCS
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ApnType {
@@ -356,6 +362,17 @@ public class ApnSetting implements Parcelable {
     @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 @@ public class ApnSetting implements Parcelable {
     @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 @@ public class ApnSetting implements Parcelable {
         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 @@ public class ApnSetting implements Parcelable {
         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 @@ public class ApnSetting implements Parcelable {
     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 @@ public class ApnSetting implements Parcelable {
         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 @@ public class ApnSetting implements Parcelable {
         this.mCarrierId = builder.mCarrierId;
         this.mSkip464Xlat = builder.mSkip464Xlat;
         this.mAlwaysOn = builder.mAlwaysOn;
+        this.mInfrastructureBitmask = builder.mInfrastructureBitmask;
     }
 
     /**
@@ -1031,6 +1095,8 @@ public class ApnSetting implements Parcelable {
                         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 @@ public class ApnSetting implements Parcelable {
                 .setCarrierId(apn.mCarrierId)
                 .setSkip464Xlat(apn.mSkip464Xlat)
                 .setAlwaysOn(apn.mAlwaysOn)
+                .setInfrastructureBitmask(apn.mInfrastructureBitmask)
                 .buildWithoutCheck();
     }
 
@@ -1115,6 +1182,7 @@ public class ApnSetting implements Parcelable {
         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 @@ public class ApnSetting implements Parcelable {
                 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 @@ public class ApnSetting implements Parcelable {
         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 @@ public class ApnSetting implements Parcelable {
                 && 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 @@ public class ApnSetting implements Parcelable {
                 && 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 @@ public class ApnSetting implements Parcelable {
         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 @@ public class ApnSetting implements Parcelable {
         dest.writeInt(mCarrierId);
         dest.writeInt(mSkip464Xlat);
         dest.writeBoolean(mAlwaysOn);
+        dest.writeInt(mInfrastructureBitmask);
     }
 
     private static ApnSetting readFromParcel(Parcel in) {
@@ -1686,6 +1759,7 @@ public class ApnSetting implements Parcelable {
                 .setCarrierId(in.readInt())
                 .setSkip464Xlat(in.readInt())
                 .setAlwaysOn(in.readBoolean())
+                .setInfrastructureBitmask(in.readInt())
                 .buildWithoutCheck();
     }
 
@@ -1767,6 +1841,7 @@ public class ApnSetting implements Parcelable {
         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.
@@ -2188,6 +2263,22 @@ public class ApnSetting implements Parcelable {
             return this;
         }
 
+        /**
+         * 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.
          *
@@ -2198,7 +2289,7 @@ public class ApnSetting implements Parcelable {
         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 f346b9208bcdd855c0a62022068b2837ab658f4a..88a32d15a7a60bd749980fa45981131971195943 100644
--- a/telephony/java/android/telephony/data/DataProfile.java
+++ b/telephony/java/android/telephony/data/DataProfile.java
@@ -431,6 +431,8 @@ public final class DataProfile implements Parcelable {
                 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/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index 82aa85d55e4c6ca845097257cb67777c4a7179bb..f4f2be642e814044de986ac735712eaa84fb063f 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -53,7 +53,17 @@ filegroup {
 }
 
 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 @@ android_test {
     exclude_srcs: [
         ":FlickerTestsAppClose-src",
         ":FlickerTestsIme-src",
-        ":FlickerTestsAppLaunch-src",
+        ":FlickerTestsAppLaunch1-src",
+        ":FlickerTestsAppLaunch2-src",
         ":FlickerTestsQuickswitch-src",
         ":FlickerTestsRotation-src",
         ":FlickerTestsNotification-src",
@@ -162,10 +173,44 @@ android_test {
     instrumentation_target_package: "com.android.server.wm.flicker.launch",
     srcs: [
         ":FlickerTestsBase-src",
-        ":FlickerTestsAppLaunch-src",
+        ":FlickerTestsAppLaunchCommon-src",
+        ":FlickerTestsAppLaunch2-src",
+    ],
+    exclude_srcs: [
+        ":FlickerTestsActivityEmbedding-src",
+    ],
+}
+
+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",
     ],
 }
 
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 48d504141116f35f0058fb3f7a3bcddecb8e563b..f788efae4c59441f7a0bc7bcaa3d1e81eb24056a 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 @@ import org.junit.runners.Parameterized
 @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 f575fcc0e94586f0a8d878be00548788c4dedd99..d86dc50b3a5cb2d9f8ce5f3e844767f4ffa681ec 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.junit.FlickerParametersRunnerFactory
 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 93d0520d87bc3c0d1cf3c15a4a0211efd88da7c6..be07053f3039a109ea82766207727f439b81dc4a 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.LegacyFlickerTest
 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 78b58f4da065b78dab18a7a2fca6c601969028cd..f66eff9b4cb05ff5da297ae46adfe2f5c6a63b56 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.LegacyFlickerTest
 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 3f931c48ddbde50546bfdf8aee9b90349d93d940..65214764f0ac552e1095648d2b0438f7894491a4 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 android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 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 b85362a075386857cccbcd0f9add9c85065eb17e..4d31c28b1231a0a15cae27388168f11845aae519 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.LegacyFlickerTest
 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 1bdb6e717b12598fb2d2c1cbf1e12dcf66d11be1..42e34b313d8ef8f165809b960a7602679521985a 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.tools.device.flicker.rules.RemoveAllTasksButHomeRule
 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 3d9c0679945df7058ff6bd2c7669939634bd4db1..0000000000000000000000000000000000000000
--- 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 0000000000000000000000000000000000000000..c8547015b92bc7315d46450975b89ba4ac291257
--- /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 96%
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 4fc9bcb309c5f71d2cc06d691dd15d3fbb908ddb..9d7096ea0e73c1183ea10b9974b46ebcfba6c5c6 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 cc501e6c43081ac44d1a649d0b8a278f7c86fbc1..7b088431b0bb3c0bd27fd499e2a38857809fc80f 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 bb11be5bb520d32f508428f361860492662c3e8c..989619e08d98681c0b8b563c4416d6e9f001f759 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 0000000000000000000000000000000000000000..2e9620bb13c5a329bb2eec0a6a6f25a602b52fd9
--- /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 49cbde0d4611a29c2f42b7efd1272f4acf3e7b92..d2537f6410e8c5661c75bc13712746a7d10fad91 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/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
index 01f15916f244fd5193fadc4507bd1bebcc9c5014..3d5a0f7a239f4cbcae5ee388594e3f19540d05cc 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 @@ public final class NetworkProviderInfo implements Parcelable {
          * @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 @@ public final class NetworkProviderInfo implements Parcelable {
      *
      * @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 71ac94ba2ee433dba781d8f744f3bb372c9fac4a..b0f68f7870eee705841b292a3ff11c641aaf656b 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 @@ public class SharedConnectivityManager {
      */
     @TestApi
     @NonNull
+    @FlaggedApi("com.android.wifi.flags.shared_connectivity_broadcast_receiver_test_api")
     public BroadcastReceiver getBroadcastReceiver() {
         return mBroadcastReceiver;
     }