Merge "Add logs and traces when draw doesn't run" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 6d74a84..b924ac8 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -22,6 +22,7 @@
     ":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}",
@@ -51,6 +52,7 @@
     ":aconfig_midi_flags_java_lib{.generated_srcjars}",
     ":android.service.autofill.flags-aconfig-java{.generated_srcjars}",
     ":com.android.net.flags-aconfig-java{.generated_srcjars}",
+    ":device_policy_aconfig_flags_lib{.generated_srcjars}",
 ]
 
 filegroup {
@@ -523,3 +525,36 @@
     aconfig_declarations: "com.android.net.flags-aconfig",
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
+
+// DevicePolicy
+aconfig_declarations {
+    name: "device_policy_aconfig_flags",
+    package: "android.app.admin.flags",
+    srcs: [
+        "core/java/android/app/admin/flags/flags.aconfig",
+    ],
+}
+
+java_aconfig_library {
+    name: "device_policy_aconfig_flags_lib",
+    aconfig_declarations: "device_policy_aconfig_flags",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+cc_aconfig_library {
+    name: "device_policy_aconfig_flags_c_lib",
+    aconfig_declarations: "device_policy_aconfig_flags",
+}
+
+// Notifications
+aconfig_declarations {
+    name: "android.service.notification.flags-aconfig",
+    package: "android.service.notification",
+    srcs: ["core/java/android/service/notification/flags.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.service.notification.flags-aconfig-java",
+    aconfig_declarations: "android.service.notification.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/core/api/current.txt b/core/api/current.txt
index 66aeb0f..51cdfc5 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -668,6 +668,7 @@
     field public static final int debuggable = 16842767; // 0x101000f
     field public static final int defaultFocusHighlightEnabled = 16844130; // 0x1010562
     field public static final int defaultHeight = 16844021; // 0x10104f5
+    field @FlaggedApi("android.content.res.default_locale") public static final int defaultLocale;
     field public static final int defaultToDeviceProtectedStorage = 16844036; // 0x1010504
     field public static final int defaultValue = 16843245; // 0x10101ed
     field public static final int defaultWidth = 16844020; // 0x10104f4
@@ -6188,6 +6189,7 @@
     ctor public LocaleConfig(@NonNull android.os.LocaleList);
     method public int describeContents();
     method @NonNull public static android.app.LocaleConfig fromContextIgnoringOverride(@NonNull android.content.Context);
+    method @FlaggedApi("android.content.res.default_locale") @Nullable public java.util.Locale getDefaultLocale();
     method public int getStatus();
     method @Nullable public android.os.LocaleList getSupportedLocales();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -9530,7 +9532,7 @@
     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 void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.companion.AssociationInfo> CREATOR;
   }
@@ -9600,7 +9602,7 @@
     method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public void attachSystemDataTransport(int, @NonNull java.io.InputStream, @NonNull java.io.OutputStream) throws android.companion.DeviceNotAssociatedException;
     method @Nullable public android.content.IntentSender buildAssociationCancellationIntent();
     method @Nullable public android.content.IntentSender buildPermissionTransferUserConsentIntent(int) throws android.companion.DeviceNotAssociatedException;
-    method public void clearAssociationTag(int);
+    method @FlaggedApi("android.companion.association_tag") public void clearAssociationTag(int);
     method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public void detachSystemDataTransport(int) throws android.companion.DeviceNotAssociatedException;
     method public void disableSystemDataSyncForTypes(int, int);
     method @Deprecated public void disassociate(@NonNull String);
@@ -9610,7 +9612,7 @@
     method @NonNull public java.util.List<android.companion.AssociationInfo> getMyAssociations();
     method @Deprecated public boolean hasNotificationAccess(android.content.ComponentName);
     method public void requestNotificationAccess(android.content.ComponentName);
-    method public void setAssociationTag(int, @NonNull String);
+    method @FlaggedApi("android.companion.association_tag") public void setAssociationTag(int, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
     method public void startSystemDataTransfer(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.companion.CompanionException>) throws android.companion.DeviceNotAssociatedException;
     method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
@@ -11719,7 +11721,7 @@
     method @NonNull public void setResourceValue(@NonNull String, @IntRange(from=android.util.TypedValue.TYPE_FIRST_INT, to=android.util.TypedValue.TYPE_LAST_INT) int, int, @Nullable String);
     method @NonNull public void setResourceValue(@NonNull String, int, @NonNull String, @Nullable String);
     method @NonNull public void setResourceValue(@NonNull String, @NonNull android.os.ParcelFileDescriptor, @Nullable String);
-    method @NonNull public void setResourceValue(@NonNull String, @NonNull android.content.res.AssetFileDescriptor, @Nullable String);
+    method @FlaggedApi("android.content.res.asset_file_descriptor_frro") @NonNull public void setResourceValue(@NonNull String, @NonNull android.content.res.AssetFileDescriptor, @Nullable String);
     method public void setTargetOverlayable(@Nullable String);
   }
 
@@ -12675,6 +12677,7 @@
     method public boolean isDeviceUpgrading();
     method public abstract boolean isInstantApp();
     method public abstract boolean isInstantApp(@NonNull String);
+    method @FlaggedApi("android.content.pm.quarantined_enabled") public boolean isPackageQuarantined(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
     method @FlaggedApi("android.content.pm.stay_stopped") public boolean isPackageStopped(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public boolean isPackageSuspended(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public boolean isPackageSuspended();
@@ -12913,6 +12916,7 @@
     field public static final int MATCH_DIRECT_BOOT_UNAWARE = 262144; // 0x40000
     field public static final int MATCH_DISABLED_COMPONENTS = 512; // 0x200
     field public static final int MATCH_DISABLED_UNTIL_USED_COMPONENTS = 32768; // 0x8000
+    field @FlaggedApi("android.content.pm.quarantined_enabled") public static final long MATCH_QUARANTINED_COMPONENTS = 4294967296L; // 0x100000000L
     field public static final int MATCH_SYSTEM_ONLY = 1048576; // 0x100000
     field public static final int MATCH_UNINSTALLED_PACKAGES = 8192; // 0x2000
     field public static final long MAXIMUM_VERIFICATION_TIMEOUT = 3600000L; // 0x36ee80L
@@ -14352,14 +14356,14 @@
   public final class SQLiteDatabase extends android.database.sqlite.SQLiteClosable {
     method public void beginTransaction();
     method public void beginTransactionNonExclusive();
-    method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public void beginTransactionReadOnly();
+    method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public void beginTransactionReadOnly();
     method public void beginTransactionWithListener(@Nullable android.database.sqlite.SQLiteTransactionListener);
     method public void beginTransactionWithListenerNonExclusive(@Nullable android.database.sqlite.SQLiteTransactionListener);
-    method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public void beginTransactionWithListenerReadOnly(@Nullable android.database.sqlite.SQLiteTransactionListener);
+    method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public void beginTransactionWithListenerReadOnly(@Nullable android.database.sqlite.SQLiteTransactionListener);
     method public android.database.sqlite.SQLiteStatement compileStatement(String) throws android.database.SQLException;
     method @NonNull public static android.database.sqlite.SQLiteDatabase create(@Nullable android.database.sqlite.SQLiteDatabase.CursorFactory);
     method @NonNull public static android.database.sqlite.SQLiteDatabase createInMemory(@NonNull android.database.sqlite.SQLiteDatabase.OpenParams);
-    method @FlaggedApi("android.database.sqlite.sqlite_apis_15") @NonNull public android.database.sqlite.SQLiteRawStatement createRawStatement(@NonNull String);
+    method @FlaggedApi("android.database.sqlite.sqlite_apis_35") @NonNull public android.database.sqlite.SQLiteRawStatement createRawStatement(@NonNull String);
     method public int delete(@NonNull String, @Nullable String, @Nullable String[]);
     method public static boolean deleteDatabase(@NonNull java.io.File);
     method public void disableWriteAheadLogging();
@@ -14370,13 +14374,13 @@
     method public void execSQL(@NonNull String, @NonNull Object[]) throws android.database.SQLException;
     method public static String findEditTable(String);
     method public java.util.List<android.util.Pair<java.lang.String,java.lang.String>> getAttachedDbs();
-    method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public long getLastChangedRowCount();
-    method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public long getLastInsertRowId();
+    method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public long getLastChangedRowCount();
+    method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public long getLastInsertRowId();
     method public long getMaximumSize();
     method public long getPageSize();
     method public String getPath();
     method @Deprecated public java.util.Map<java.lang.String,java.lang.String> getSyncedTables();
-    method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public long getTotalChangedRowCount();
+    method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public long getTotalChangedRowCount();
     method public int getVersion();
     method public boolean inTransaction();
     method public long insert(@NonNull String, @Nullable String, @Nullable android.content.ContentValues);
@@ -14598,7 +14602,7 @@
     method public int update(@NonNull android.database.sqlite.SQLiteDatabase, @NonNull android.content.ContentValues, @Nullable String, @Nullable String[]);
   }
 
-  @FlaggedApi("android.database.sqlite.sqlite_apis_15") public final class SQLiteRawStatement implements java.io.Closeable {
+  @FlaggedApi("android.database.sqlite.sqlite_apis_35") public final class SQLiteRawStatement implements java.io.Closeable {
     method public void bindBlob(int, @NonNull byte[]) throws android.database.sqlite.SQLiteException;
     method public void bindBlob(int, @NonNull byte[], int, int) throws android.database.sqlite.SQLiteException;
     method public void bindDouble(int, double) throws android.database.sqlite.SQLiteException;
@@ -18547,12 +18551,12 @@
     ctor @Deprecated public BiometricPrompt.CryptoObject(@NonNull android.security.identity.IdentityCredential);
     ctor public BiometricPrompt.CryptoObject(@NonNull android.security.identity.PresentationSession);
     ctor @FlaggedApi("android.hardware.biometrics.add_key_agreement_crypto_object") public BiometricPrompt.CryptoObject(@NonNull javax.crypto.KeyAgreement);
-    method public javax.crypto.Cipher getCipher();
+    method @Nullable public javax.crypto.Cipher getCipher();
     method @Deprecated @Nullable public android.security.identity.IdentityCredential getIdentityCredential();
     method @FlaggedApi("android.hardware.biometrics.add_key_agreement_crypto_object") @Nullable public javax.crypto.KeyAgreement getKeyAgreement();
-    method public javax.crypto.Mac getMac();
+    method @Nullable public javax.crypto.Mac getMac();
     method @Nullable public android.security.identity.PresentationSession getPresentationSession();
-    method public java.security.Signature getSignature();
+    method @Nullable public java.security.Signature getSignature();
   }
 
 }
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 5d1f6dc..c3c63b5 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -9538,7 +9538,7 @@
     method public int getDeviceType();
     method @NonNull public android.os.Bundle getExtras();
     method @NonNull public String getModelName();
-    method public boolean isBatteryCharging();
+    method @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status") public boolean isBatteryCharging();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.sharedconnectivity.app.NetworkProviderInfo> CREATOR;
     field public static final int DEVICE_TYPE_AUTO = 5; // 0x5
@@ -9552,7 +9552,7 @@
   public static final class NetworkProviderInfo.Builder {
     ctor public NetworkProviderInfo.Builder(@NonNull String, @NonNull String);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo build();
-    method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryCharging(boolean);
+    method @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status") @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryCharging(boolean);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryPercentage(@IntRange(from=0, to=100) int);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=4) int);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceName(@NonNull String);
@@ -9641,6 +9641,7 @@
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler);
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean setControllerAlwaysOn(boolean);
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setReaderMode(boolean);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int setTagIntentAppPreferenceForUser(int, @NonNull String, boolean);
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
     field public static final int TAG_INTENT_APP_PREF_RESULT_PACKAGE_NOT_FOUND = -1; // 0xffffffff
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 4fb7b6b..a501031 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -251,10 +251,6 @@
     New API must be flagged with @FlaggedApi: method android.media.audiopolicy.AudioMixingRule.writeToParcel(android.os.Parcel,int)
 UnflaggedApi: android.media.audiopolicy.AudioPolicy#updateMixingRules(java.util.List<android.util.Pair<android.media.audiopolicy.AudioMix,android.media.audiopolicy.AudioMixingRule>>):
     New API must be flagged with @FlaggedApi: method android.media.audiopolicy.AudioPolicy.updateMixingRules(java.util.List<android.util.Pair<android.media.audiopolicy.AudioMix,android.media.audiopolicy.AudioMixingRule>>)
-UnflaggedApi: android.net.wifi.sharedconnectivity.app.NetworkProviderInfo#isBatteryCharging():
-    New API must be flagged with @FlaggedApi: method android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.isBatteryCharging()
-UnflaggedApi: android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder#setBatteryCharging(boolean):
-    New API must be flagged with @FlaggedApi: method android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder.setBatteryCharging(boolean)
 UnflaggedApi: android.nfc.cardemulation.AidGroup#CONTENTS_FILE_DESCRIPTOR:
     New API must be flagged with @FlaggedApi: field android.nfc.cardemulation.AidGroup.CONTENTS_FILE_DESCRIPTOR
 UnflaggedApi: android.nfc.cardemulation.AidGroup#PARCELABLE_WRITE_RETURN_VALUE:
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index df556a8..03a58be 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -541,7 +541,7 @@
     field public static final String PERMITTED_INPUT_METHODS_POLICY = "permittedInputMethods";
     field public static final String PERSONAL_APPS_SUSPENDED_POLICY = "personalAppsSuspended";
     field public static final String SCREEN_CAPTURE_DISABLED_POLICY = "screenCaptureDisabled";
-    field public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling";
+    field @FlaggedApi("android.app.admin.flags.policy_engine_migration_v2_enabled") public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling";
   }
 
   public class DevicePolicyManager {
@@ -851,7 +851,7 @@
     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 @@
 
   public class SharedConnectivityManager {
     method @Nullable public static android.net.wifi.sharedconnectivity.app.SharedConnectivityManager create(@NonNull android.content.Context, @NonNull String, @NonNull String);
-    method @NonNull public android.content.BroadcastReceiver getBroadcastReceiver();
+    method @FlaggedApi("com.android.wifi.flags.shared_connectivity_broadcast_receiver_test_api") @NonNull public android.content.BroadcastReceiver getBroadcastReceiver();
     method @Nullable public android.content.ServiceConnection getServiceConnection();
     method public void setService(@Nullable android.os.IInterface);
   }
@@ -2148,7 +2148,7 @@
   }
 
   public final class BugreportParams {
-    field public static final int BUGREPORT_MODE_MAX_VALUE = 7; // 0x7
+    field @FlaggedApi("android.os.bugreport_mode_max_value") public static final int BUGREPORT_MODE_MAX_VALUE = 7; // 0x7
   }
 
   public class Build {
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index 93e39d5..105e764 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -257,8 +257,6 @@
     New API must be flagged with @FlaggedApi: method android.media.soundtrigger.SoundTriggerManager.loadSoundModel(android.hardware.soundtrigger.SoundTrigger.SoundModel)
 UnflaggedApi: android.media.soundtrigger.SoundTriggerManager.Model#getSoundModel():
     New API must be flagged with @FlaggedApi: method android.media.soundtrigger.SoundTriggerManager.Model.getSoundModel()
-UnflaggedApi: android.net.wifi.sharedconnectivity.app.SharedConnectivityManager#getBroadcastReceiver():
-    New API must be flagged with @FlaggedApi: method android.net.wifi.sharedconnectivity.app.SharedConnectivityManager.getBroadcastReceiver()
 UnflaggedApi: android.os.BatteryManager#BATTERY_PLUGGED_ANY:
     New API must be flagged with @FlaggedApi: field android.os.BatteryManager.BATTERY_PLUGGED_ANY
 UnflaggedApi: android.os.BugreportParams#BUGREPORT_MODE_MAX_VALUE:
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 0293f66..ddb221f 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -14,6 +14,15 @@
     hdrs: ["android/hardware/HardwareBuffer.aidl"],
 }
 
+// TODO (b/303286040): Remove this once |ENABLE_NFC_MAINLINE_FLAG| is rolled out
+filegroup {
+    name: "framework-core-nfc-infcadapter-sources",
+    srcs: [
+        "android/nfc/INfcAdapter.aidl",
+    ],
+    visibility: ["//frameworks/base/services/core"],
+}
+
 filegroup {
     name: "framework-core-sources",
     srcs: [
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index a538247..08c18c8 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -3483,12 +3483,10 @@
 
         // only do this if the user already has more than one preferred locale
         if (r.getConfiguration().getLocales().size() > 1) {
-            LocaleConfig lc = LocaleConfig.fromContextIgnoringOverride(this);
-            mResourcesManager.setLocaleList(lc != null
-                    && lc.getSupportedLocales() != null
-                    && !lc.getSupportedLocales().isEmpty()
-                    ? lc.getSupportedLocales()
-                    : null);
+            LocaleConfig lc = getUserId() < 0
+                    ? LocaleConfig.fromContextIgnoringOverride(this)
+                    : new LocaleConfig(this);
+            mResourcesManager.setLocaleConfig(lc);
         }
     }
 
diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java
index 1fdc516..369a781 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.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.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 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 @@
         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 @@
         }
         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;
+            }
+        }
     }
 
     /**
@@ -224,6 +247,17 @@
     }
 
     /**
+     * Returns the default locale if specified, otherwise null
+     *
+     * @return The default Locale or null
+     */
+    @SuppressLint("UseIcu")
+    @FlaggedApi(android.content.res.Flags.FLAG_DEFAULT_LOCALE)
+    public @Nullable Locale getDefaultLocale() {
+        return mDefaultLocale;
+    }
+
+    /**
      * Get the status of reading the resource file where the LocaleConfig was stored.
      *
      * <p>Distinguish "the application didn't provide the resource file" from "the application
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 1ecb5d3..6009c29 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -120,9 +120,9 @@
     private final ReferenceQueue<Resources> mResourcesReferencesQueue = new ReferenceQueue<>();
 
     /**
-     * The list of locales the app declares it supports.
+     * The localeConfig of the app.
      */
-    private LocaleList mLocaleList = LocaleList.getEmptyLocaleList();
+    private LocaleConfig mLocaleConfig = new LocaleConfig(LocaleList.getEmptyLocaleList());
 
     private static class ApkKey {
         public final String path;
@@ -1612,18 +1612,19 @@
     }
 
     /**
-     * Returns the LocaleList current set
+     * Returns the LocaleConfig current set
      */
-    public LocaleList getLocaleList() {
-        return mLocaleList;
+    public LocaleConfig getLocaleConfig() {
+        return mLocaleConfig;
     }
 
     /**
-     * Sets the LocaleList of app's supported locales
+     * Sets the LocaleConfig of the app
      */
-    public void setLocaleList(LocaleList localeList) {
-        if ((localeList != null) && !localeList.isEmpty()) {
-            mLocaleList = localeList;
+    public void setLocaleConfig(LocaleConfig localeConfig) {
+        if ((localeConfig != null) && (localeConfig.getSupportedLocales() != null)
+                && !localeConfig.getSupportedLocales().isEmpty()) {
+            mLocaleConfig = localeConfig;
         }
     }
 
diff --git a/core/java/android/app/admin/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java
index ad0af72..84b1ca5 100644
--- a/core/java/android/app/admin/DevicePolicyIdentifiers.java
+++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java
@@ -16,10 +16,13 @@
 
 package android.app.admin;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.TestApi;
+import android.app.admin.flags.Flags;
 import android.os.UserManager;
 
+
 import java.util.Objects;
 
 /**
@@ -164,6 +167,7 @@
      *
      * @hide
      */
+    @FlaggedApi(Flags.FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED)
     @TestApi
     public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling";
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java b/core/java/android/app/admin/flags/FlagUtils.java
similarity index 68%
rename from services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java
rename to core/java/android/app/admin/flags/FlagUtils.java
index 7e17ef11..7c3c3d5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java
+++ b/core/java/android/app/admin/flags/FlagUtils.java
@@ -14,15 +14,20 @@
  * limitations under the License.
  */
 
-package com.android.server.devicepolicy.flags;
+package android.app.admin.flags;
 
-import static com.android.server.devicepolicy.flags.Flags.devicePolicySizeTrackingEnabled;
-import static com.android.server.devicepolicy.flags.Flags.policyEngineMigrationV2Enabled;
+import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
+import static android.app.admin.flags.Flags.policyEngineMigrationV2Enabled;
+import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
 
 import android.os.Binder;
 
+/**
+ *
+ * @hide
+ */
 public final class FlagUtils {
-    private FlagUtils(){}
+    private FlagUtils() {}
 
     public static boolean isPolicyEngineMigrationV2Enabled() {
         return Binder.withCleanCallingIdentity(() -> {
@@ -35,4 +40,10 @@
             return devicePolicySizeTrackingEnabled();
         });
     }
+
+    public static boolean isOnboardingBugreportV2Enabled() {
+        return Binder.withCleanCallingIdentity(() -> {
+            return onboardingBugreportV2Enabled();
+        });
+    }
 }
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
new file mode 100644
index 0000000..c145c02
--- /dev/null
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -0,0 +1,22 @@
+package: "android.app.admin.flags"
+
+flag {
+  name: "policy_engine_migration_v2_enabled"
+  namespace: "enterprise"
+  description: "V2 of the policy engine migrations for Android V"
+  bug: "289520697"
+}
+
+flag {
+  name: "device_policy_size_tracking_enabled"
+  namespace: "enterprise"
+  description: "Add feature to track the total policy size and have a max threshold."
+  bug: "281543351"
+}
+
+flag {
+  name: "onboarding_bugreport_v2_enabled"
+  namespace: "enterprise"
+  description: "Add feature to track required changes for enabled V2 of auto-capturing of onboarding bug reports."
+  bug: "302517677"
+}
\ No newline at end of file
diff --git a/core/java/android/app/servertransaction/ActivityLifecycleItem.java b/core/java/android/app/servertransaction/ActivityLifecycleItem.java
index b34f678..06bff5d 100644
--- a/core/java/android/app/servertransaction/ActivityLifecycleItem.java
+++ b/core/java/android/app/servertransaction/ActivityLifecycleItem.java
@@ -58,6 +58,11 @@
         super(in);
     }
 
+    @Override
+    boolean isActivityLifecycleItem() {
+        return true;
+    }
+
     /** A final lifecycle state that an activity should reach. */
     @LifecycleState
     public abstract int getTargetState();
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 8617386..f7f901b 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -45,6 +45,13 @@
  */
 public class ClientTransaction implements Parcelable, ObjectPoolItem {
 
+    /**
+     * List of transaction items that should be executed in order. Including both
+     * {@link ActivityLifecycleItem} and other {@link ClientTransactionItem}.
+     */
+    @Nullable
+    private List<ClientTransactionItem> mTransactionItems;
+
     /** A list of individual callbacks to a client. */
     @UnsupportedAppUsage
     private List<ClientTransactionItem> mActivityCallbacks;
@@ -64,9 +71,32 @@
     }
 
     /**
-     * Add a message to the end of the sequence of callbacks.
-     * @param activityCallback A single message that can contain a lifecycle request/callback.
+     * Adds a message to the end of the sequence of transaction items.
+     * @param item A single message that can contain a client activity/window request/callback.
+     * TODO(b/260873529): replace both {@link #addCallback} and {@link #setLifecycleStateRequest}.
      */
+    public void addTransactionItem(@NonNull ClientTransactionItem item) {
+        if (mTransactionItems == null) {
+            mTransactionItems = new ArrayList<>();
+        }
+        mTransactionItems.add(item);
+    }
+
+    /**
+     * Gets the list of client window requests/callbacks.
+     * TODO(b/260873529): must be non null after remove the deprecated methods.
+     */
+    @Nullable
+    public List<ClientTransactionItem> getTransactionItems() {
+        return mTransactionItems;
+    }
+
+    /**
+     * Adds a message to the end of the sequence of callbacks.
+     * @param activityCallback A single message that can contain a lifecycle request/callback.
+     * @deprecated use {@link #addTransactionItem(ClientTransactionItem)} instead.
+     */
+    @Deprecated
     public void addCallback(@NonNull ClientTransactionItem activityCallback) {
         if (mActivityCallbacks == null) {
             mActivityCallbacks = new ArrayList<>();
@@ -74,25 +104,35 @@
         mActivityCallbacks.add(activityCallback);
     }
 
-    /** Get the list of callbacks. */
+    /**
+     * Gets the list of callbacks.
+     * @deprecated use {@link #getTransactionItems()} instead.
+     */
     @Nullable
     @VisibleForTesting
     @UnsupportedAppUsage
+    @Deprecated
     public List<ClientTransactionItem> getCallbacks() {
         return mActivityCallbacks;
     }
 
-    /** Get the target state lifecycle request. */
+    /**
+     * Gets the target state lifecycle request.
+     * @deprecated use {@link #getTransactionItems()} instead.
+     */
     @VisibleForTesting(visibility = PACKAGE)
     @UnsupportedAppUsage
+    @Deprecated
     public ActivityLifecycleItem getLifecycleStateRequest() {
         return mLifecycleStateRequest;
     }
 
     /**
-     * Set the lifecycle state in which the client should be after executing the transaction.
+     * Sets the lifecycle state in which the client should be after executing the transaction.
      * @param stateRequest A lifecycle request initialized with right parameters.
+     * @deprecated use {@link #addTransactionItem(ClientTransactionItem)} instead.
      */
+    @Deprecated
     public void setLifecycleStateRequest(@NonNull ActivityLifecycleItem stateRequest) {
         mLifecycleStateRequest = stateRequest;
     }
@@ -103,6 +143,14 @@
      *                                 requested by transaction items.
      */
     public void preExecute(@NonNull ClientTransactionHandler clientTransactionHandler) {
+        if (mTransactionItems != null) {
+            final int size = mTransactionItems.size();
+            for (int i = 0; i < size; ++i) {
+                mTransactionItems.get(i).preExecute(clientTransactionHandler);
+            }
+            return;
+        }
+
         if (mActivityCallbacks != null) {
             final int size = mActivityCallbacks.size();
             for (int i = 0; i < size; ++i) {
@@ -147,12 +195,19 @@
 
     @Override
     public void recycle() {
+        if (mTransactionItems != null) {
+            int size = mTransactionItems.size();
+            for (int i = 0; i < size; i++) {
+                mTransactionItems.get(i).recycle();
+            }
+            mTransactionItems = null;
+        }
         if (mActivityCallbacks != null) {
             int size = mActivityCallbacks.size();
             for (int i = 0; i < size; i++) {
                 mActivityCallbacks.get(i).recycle();
             }
-            mActivityCallbacks.clear();
+            mActivityCallbacks = null;
         }
         if (mLifecycleStateRequest != null) {
             mLifecycleStateRequest.recycle();
@@ -165,8 +220,15 @@
     // Parcelable implementation
 
     /** Write to Parcel. */
+    @SuppressWarnings("AndroidFrameworkEfficientParcelable") // Item class is not final.
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
+        final boolean writeTransactionItems = mTransactionItems != null;
+        dest.writeBoolean(writeTransactionItems);
+        if (writeTransactionItems) {
+            dest.writeParcelableList(mTransactionItems, flags);
+        }
+
         dest.writeParcelable(mLifecycleStateRequest, flags);
         final boolean writeActivityCallbacks = mActivityCallbacks != null;
         dest.writeBoolean(writeActivityCallbacks);
@@ -177,11 +239,20 @@
 
     /** Read from Parcel. */
     private ClientTransaction(@NonNull Parcel in) {
-        mLifecycleStateRequest = in.readParcelable(getClass().getClassLoader(), android.app.servertransaction.ActivityLifecycleItem.class);
+        final boolean readTransactionItems = in.readBoolean();
+        if (readTransactionItems) {
+            mTransactionItems = new ArrayList<>();
+            in.readParcelableList(mTransactionItems, getClass().getClassLoader(),
+                    ClientTransactionItem.class);
+        }
+
+        mLifecycleStateRequest = in.readParcelable(getClass().getClassLoader(),
+                ActivityLifecycleItem.class);
         final boolean readActivityCallbacks = in.readBoolean();
         if (readActivityCallbacks) {
             mActivityCallbacks = new ArrayList<>();
-            in.readParcelableList(mActivityCallbacks, getClass().getClassLoader(), android.app.servertransaction.ClientTransactionItem.class);
+            in.readParcelableList(mActivityCallbacks, getClass().getClassLoader(),
+                    ClientTransactionItem.class);
         }
     }
 
@@ -209,7 +280,8 @@
             return false;
         }
         final ClientTransaction other = (ClientTransaction) o;
-        return Objects.equals(mActivityCallbacks, other.mActivityCallbacks)
+        return Objects.equals(mTransactionItems, other.mTransactionItems)
+                && Objects.equals(mActivityCallbacks, other.mActivityCallbacks)
                 && Objects.equals(mLifecycleStateRequest, other.mLifecycleStateRequest)
                 && mClient == other.mClient;
     }
@@ -217,6 +289,7 @@
     @Override
     public int hashCode() {
         int result = 17;
+        result = 31 * result + Objects.hashCode(mTransactionItems);
         result = 31 * result + Objects.hashCode(mActivityCallbacks);
         result = 31 * result + Objects.hashCode(mLifecycleStateRequest);
         result = 31 * result + Objects.hashCode(mClient);
@@ -227,6 +300,22 @@
     void dump(@NonNull String prefix, @NonNull PrintWriter pw,
             @NonNull ClientTransactionHandler transactionHandler) {
         pw.append(prefix).println("ClientTransaction{");
+        if (mTransactionItems != null) {
+            pw.append(prefix).print("  transactionItems=[");
+            final String itemPrefix = prefix + "    ";
+            final int size = mTransactionItems.size();
+            if (size > 0) {
+                pw.println();
+                for (int i = 0; i < size; i++) {
+                    mTransactionItems.get(i).dump(itemPrefix, pw, transactionHandler);
+                }
+                pw.append(prefix).println("  ]");
+            } else {
+                pw.println("]");
+            }
+            pw.append(prefix).println("}");
+            return;
+        }
         pw.append(prefix).print("  callbacks=[");
         final String itemPrefix = prefix + "    ";
         final int size = mActivityCallbacks != null ? mActivityCallbacks.size() : 0;
diff --git a/core/java/android/app/servertransaction/ClientTransactionItem.java b/core/java/android/app/servertransaction/ClientTransactionItem.java
index 07e5a7d..f94e22d 100644
--- a/core/java/android/app/servertransaction/ClientTransactionItem.java
+++ b/core/java/android/app/servertransaction/ClientTransactionItem.java
@@ -72,6 +72,13 @@
         return null;
     }
 
+    /**
+     * Whether this is a {@link ActivityLifecycleItem}.
+     */
+    boolean isActivityLifecycleItem() {
+        return false;
+    }
+
     /** Dumps this transaction item. */
     void dump(@NonNull String prefix, @NonNull PrintWriter pw,
             @NonNull ClientTransactionHandler transactionHandler) {
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index 066f9fe..9f5e0dc 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -28,6 +28,7 @@
 import static android.app.servertransaction.TransactionExecutorHelper.getShortActivityName;
 import static android.app.servertransaction.TransactionExecutorHelper.getStateName;
 import static android.app.servertransaction.TransactionExecutorHelper.lastCallbackRequestingState;
+import static android.app.servertransaction.TransactionExecutorHelper.shouldExcludeLastLifecycleState;
 import static android.app.servertransaction.TransactionExecutorHelper.tId;
 import static android.app.servertransaction.TransactionExecutorHelper.transactionToString;
 
@@ -61,6 +62,9 @@
     private final PendingTransactionActions mPendingActions = new PendingTransactionActions();
     private final TransactionExecutorHelper mHelper = new TransactionExecutorHelper();
 
+    /** Keeps track of display ids whose Configuration got updated within a transaction. */
+    private final ArraySet<Integer> mConfigUpdatedDisplayIds = new ArraySet<>();
+
     /** Initialize an instance with transaction handler, that will execute all requested actions. */
     public TransactionExecutor(@NonNull ClientTransactionHandler clientTransactionHandler) {
         mTransactionHandler = clientTransactionHandler;
@@ -79,15 +83,52 @@
             Slog.d(TAG, transactionToString(transaction, mTransactionHandler));
         }
 
-        executeCallbacks(transaction);
-        executeLifecycleState(transaction);
+        if (transaction.getTransactionItems() != null) {
+            executeTransactionItems(transaction);
+        } else {
+            // TODO(b/260873529): cleanup after launch.
+            executeCallbacks(transaction);
+            executeLifecycleState(transaction);
+        }
+
+        if (!mConfigUpdatedDisplayIds.isEmpty()) {
+            // Whether this transaction should trigger DisplayListener#onDisplayChanged.
+            final ClientTransactionListenerController controller =
+                    ClientTransactionListenerController.getInstance();
+            final int displayCount = mConfigUpdatedDisplayIds.size();
+            for (int i = 0; i < displayCount; i++) {
+                final int displayId = mConfigUpdatedDisplayIds.valueAt(i);
+                controller.onDisplayChanged(displayId);
+            }
+            mConfigUpdatedDisplayIds.clear();
+        }
 
         mPendingActions.clear();
         if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "End resolving transaction");
     }
 
-    /** Cycle through all states requested by callbacks and execute them at proper times. */
+    /** Cycles through all transaction items and execute them at proper times. */
     @VisibleForTesting
+    public void executeTransactionItems(@NonNull ClientTransaction transaction) {
+        final List<ClientTransactionItem> items = transaction.getTransactionItems();
+        final int size = items.size();
+        for (int i = 0; i < size; i++) {
+            final ClientTransactionItem item = items.get(i);
+            if (item.isActivityLifecycleItem()) {
+                executeLifecycleItem(transaction, (ActivityLifecycleItem) item);
+            } else {
+                executeNonLifecycleItem(transaction, item,
+                        shouldExcludeLastLifecycleState(items, i));
+            }
+        }
+    }
+
+    /**
+     * Cycle through all states requested by callbacks and execute them at proper times.
+     * @deprecated use {@link #executeTransactionItems} instead.
+     */
+    @VisibleForTesting
+    @Deprecated
     public void executeCallbacks(@NonNull ClientTransaction transaction) {
         final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
         if (callbacks == null || callbacks.isEmpty()) {
@@ -105,83 +146,78 @@
         // Index of the last callback that requests some post-execution state.
         final int lastCallbackRequestingState = lastCallbackRequestingState(transaction);
 
-        // Keep track of display ids whose Configuration got updated with this transaction.
-        ArraySet<Integer> configUpdatedDisplays = null;
-
         final int size = callbacks.size();
         for (int i = 0; i < size; ++i) {
             final ClientTransactionItem item = callbacks.get(i);
-            final IBinder token = item.getActivityToken();
-            ActivityClientRecord r = mTransactionHandler.getActivityClient(token);
 
-            if (token != null && r == null
-                    && mTransactionHandler.getActivitiesToBeDestroyed().containsKey(token)) {
-                // The activity has not been created but has been requested to destroy, so all
-                // transactions for the token are just like being cancelled.
-                Slog.w(TAG, "Skip pre-destroyed transaction item:\n" + item);
-                continue;
-            }
-
-            if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callback: " + item);
+            // Skip the very last transition and perform it by explicit state request instead.
             final int postExecutionState = item.getPostExecutionState();
-
-            if (item.shouldHaveDefinedPreExecutionState()) {
-                final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r,
-                        item.getPostExecutionState());
-                if (closestPreExecutionState != UNDEFINED) {
-                    cycleToPath(r, closestPreExecutionState, transaction);
-                }
-            }
-
-            // Can't read flag from isolated process.
-            final boolean isSyncWindowConfigUpdateFlagEnabled = !Process.isIsolated()
-                    && syncWindowConfigUpdateFlag();
-            final Context configUpdatedContext = isSyncWindowConfigUpdateFlagEnabled
-                    ? item.getContextToUpdate(mTransactionHandler)
-                    : null;
-            final Configuration preExecutedConfig = configUpdatedContext != null
-                    ? new Configuration(configUpdatedContext.getResources().getConfiguration())
-                    : null;
-
-            item.execute(mTransactionHandler, mPendingActions);
-
-            if (configUpdatedContext != null) {
-                final Configuration postExecutedConfig = configUpdatedContext.getResources()
-                        .getConfiguration();
-                if (!areConfigurationsEqualForDisplay(postExecutedConfig, preExecutedConfig)) {
-                    if (configUpdatedDisplays == null) {
-                        configUpdatedDisplays = new ArraySet<>();
-                    }
-                    configUpdatedDisplays.add(configUpdatedContext.getDisplayId());
-                }
-            }
-
-            item.postExecute(mTransactionHandler, mPendingActions);
-            if (r == null) {
-                // Launch activity request will create an activity record.
-                r = mTransactionHandler.getActivityClient(token);
-            }
-
-            if (postExecutionState != UNDEFINED && r != null) {
-                // Skip the very last transition and perform it by explicit state request instead.
-                final boolean shouldExcludeLastTransition =
-                        i == lastCallbackRequestingState && finalState == postExecutionState;
-                cycleToPath(r, postExecutionState, shouldExcludeLastTransition, transaction);
-            }
-        }
-
-        if (configUpdatedDisplays != null) {
-            final ClientTransactionListenerController controller =
-                    ClientTransactionListenerController.getInstance();
-            final int displayCount = configUpdatedDisplays.size();
-            for (int i = 0; i < displayCount; i++) {
-                final int displayId = configUpdatedDisplays.valueAt(i);
-                controller.onDisplayChanged(displayId);
-            }
+            final boolean shouldExcludeLastLifecycleState = postExecutionState != UNDEFINED
+                    && i == lastCallbackRequestingState && finalState == postExecutionState;
+            executeNonLifecycleItem(transaction, item, shouldExcludeLastLifecycleState);
         }
     }
 
-    /** Transition to the final state if requested by the transaction. */
+    private void executeNonLifecycleItem(@NonNull ClientTransaction transaction,
+            @NonNull ClientTransactionItem item, boolean shouldExcludeLastLifecycleState) {
+        final IBinder token = item.getActivityToken();
+        ActivityClientRecord r = mTransactionHandler.getActivityClient(token);
+
+        if (token != null && r == null
+                && mTransactionHandler.getActivitiesToBeDestroyed().containsKey(token)) {
+            // The activity has not been created but has been requested to destroy, so all
+            // transactions for the token are just like being cancelled.
+            Slog.w(TAG, "Skip pre-destroyed transaction item:\n" + item);
+            return;
+        }
+
+        if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callback: " + item);
+        final int postExecutionState = item.getPostExecutionState();
+
+        if (item.shouldHaveDefinedPreExecutionState()) {
+            final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r,
+                    postExecutionState);
+            if (closestPreExecutionState != UNDEFINED) {
+                cycleToPath(r, closestPreExecutionState, transaction);
+            }
+        }
+
+        // Can't read flag from isolated process.
+        final boolean isSyncWindowConfigUpdateFlagEnabled = !Process.isIsolated()
+                && syncWindowConfigUpdateFlag();
+        final Context configUpdatedContext = isSyncWindowConfigUpdateFlagEnabled
+                ? item.getContextToUpdate(mTransactionHandler)
+                : null;
+        final Configuration preExecutedConfig = configUpdatedContext != null
+                ? new Configuration(configUpdatedContext.getResources().getConfiguration())
+                : null;
+
+        item.execute(mTransactionHandler, mPendingActions);
+
+        if (configUpdatedContext != null) {
+            final Configuration postExecutedConfig = configUpdatedContext.getResources()
+                    .getConfiguration();
+            if (!areConfigurationsEqualForDisplay(postExecutedConfig, preExecutedConfig)) {
+                mConfigUpdatedDisplayIds.add(configUpdatedContext.getDisplayId());
+            }
+        }
+
+        item.postExecute(mTransactionHandler, mPendingActions);
+        if (r == null) {
+            // Launch activity request will create an activity record.
+            r = mTransactionHandler.getActivityClient(token);
+        }
+
+        if (postExecutionState != UNDEFINED && r != null) {
+            cycleToPath(r, postExecutionState, shouldExcludeLastLifecycleState, transaction);
+        }
+    }
+
+    /**
+     * Transition to the final state if requested by the transaction.
+     * @deprecated use {@link #executeTransactionItems} instead
+     */
+    @Deprecated
     private void executeLifecycleState(@NonNull ClientTransaction transaction) {
         final ActivityLifecycleItem lifecycleItem = transaction.getLifecycleStateRequest();
         if (lifecycleItem == null) {
@@ -189,6 +225,11 @@
             return;
         }
 
+        executeLifecycleItem(transaction, lifecycleItem);
+    }
+
+    private void executeLifecycleItem(@NonNull ClientTransaction transaction,
+            @NonNull ActivityLifecycleItem lifecycleItem) {
         final IBinder token = lifecycleItem.getActivityToken();
         final ActivityClientRecord r = mTransactionHandler.getActivityClient(token);
         if (DEBUG_RESOLVER) {
diff --git a/core/java/android/app/servertransaction/TransactionExecutorHelper.java b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
index 7e89a5b..dfbccb4 100644
--- a/core/java/android/app/servertransaction/TransactionExecutorHelper.java
+++ b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
@@ -236,21 +236,39 @@
      * index 1 will be returned, because ActivityResult request on position 1 will be the last
      * request that moves activity to the RESUMED state where it will eventually end.
      */
-    static int lastCallbackRequestingState(ClientTransaction transaction) {
+    static int lastCallbackRequestingState(@NonNull ClientTransaction transaction) {
         final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
-        if (callbacks == null || callbacks.size() == 0) {
+        if (callbacks == null || callbacks.isEmpty()
+                || transaction.getLifecycleStateRequest() == null) {
             return -1;
         }
+        return lastCallbackRequestingStateIndex(callbacks, 0, callbacks.size() - 1,
+                transaction.getLifecycleStateRequest().getActivityToken());
+    }
 
+    /**
+     * Returns the index of the last callback between the start index and last index that requests
+     * the state for the given activity token in which that activity will be after execution.
+     * If there is a group of callbacks in the end that requests the same specific state or doesn't
+     * request any - we will find the first one from such group.
+     *
+     * E.g. ActivityResult requests RESUMED post-execution state, Configuration does not request any
+     * specific state. If there is a sequence
+     *   Configuration - ActivityResult - Configuration - ActivityResult
+     * index 1 will be returned, because ActivityResult request on position 1 will be the last
+     * request that moves activity to the RESUMED state where it will eventually end.
+     */
+    private static int lastCallbackRequestingStateIndex(@NonNull List<ClientTransactionItem> items,
+            int startIndex, int lastIndex, @NonNull IBinder activityToken) {
         // Go from the back of the list to front, look for the request closes to the beginning that
         // requests the state in which activity will end after all callbacks are executed.
         int lastRequestedState = UNDEFINED;
         int lastRequestingCallback = -1;
-        for (int i = callbacks.size() - 1; i >= 0; i--) {
-            final ClientTransactionItem callback = callbacks.get(i);
-            final int postExecutionState = callback.getPostExecutionState();
-            if (postExecutionState != UNDEFINED) {
-                // Found a callback that requests some post-execution state.
+        for (int i = lastIndex; i >= startIndex; i--) {
+            final ClientTransactionItem item = items.get(i);
+            final int postExecutionState = item.getPostExecutionState();
+            if (postExecutionState != UNDEFINED && activityToken.equals(item.getActivityToken())) {
+                // Found a callback that requests some post-execution state for the given activity.
                 if (lastRequestedState == UNDEFINED || lastRequestedState == postExecutionState) {
                     // It's either a first-from-end callback that requests state or it requests
                     // the same state as the last one. In both cases, we will use it as the new
@@ -266,6 +284,53 @@
         return lastRequestingCallback;
     }
 
+    /**
+     * For the transaction item at {@code currentIndex}, if it is requesting post execution state,
+     * whether or not to exclude the last state. This only returns {@code true} when there is a
+     * following explicit {@link ActivityLifecycleItem} requesting the same state for the same
+     * activity, so that last state will be covered by the following {@link ActivityLifecycleItem}.
+     */
+    static boolean shouldExcludeLastLifecycleState(@NonNull List<ClientTransactionItem> items,
+            int currentIndex) {
+        final ClientTransactionItem item = items.get(currentIndex);
+        final IBinder activityToken = item.getActivityToken();
+        final int postExecutionState = item.getPostExecutionState();
+        if (activityToken == null || postExecutionState == UNDEFINED) {
+            // Not a transaction item requesting post execution state.
+            return false;
+        }
+        final int nextLifecycleItemIndex = findNextLifecycleItemIndex(items, currentIndex + 1,
+                activityToken);
+        if (nextLifecycleItemIndex == -1) {
+            // No following ActivityLifecycleItem for this activity token.
+            return false;
+        }
+        final ActivityLifecycleItem lifecycleItem =
+                (ActivityLifecycleItem) items.get(nextLifecycleItemIndex);
+        if (postExecutionState != lifecycleItem.getTargetState()) {
+            // The explicit ActivityLifecycleItem is not requesting the same state.
+            return false;
+        }
+        // Only exclude for the first non-lifecycle item that requests the same specific state.
+        return currentIndex == lastCallbackRequestingStateIndex(items, currentIndex,
+                nextLifecycleItemIndex - 1, activityToken);
+    }
+
+    /**
+     * Finds the index of the next {@link ActivityLifecycleItem} for the given activity token.
+     */
+    private static int findNextLifecycleItemIndex(@NonNull List<ClientTransactionItem> items,
+            int startIndex, @NonNull IBinder activityToken) {
+        final int size = items.size();
+        for (int i = startIndex; i < size; i++) {
+            final ClientTransactionItem item = items.get(i);
+            if (item.isActivityLifecycleItem() && item.getActivityToken().equals(activityToken)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
     /** Dump transaction to string. */
     static String transactionToString(@NonNull ClientTransaction transaction,
             @NonNull ClientTransactionHandler transactionHandler) {
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index 6393c45..8b09bdf 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -144,6 +144,7 @@
      * @return the tag of this association.
      * @see CompanionDeviceManager#setAssociationTag(int, String)
      */
+    @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
     @Nullable
     public String getTag() {
         return mTag;
@@ -459,6 +460,7 @@
         }
 
         /** @hide */
+        @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
         @TestApi
         @NonNull
         public Builder setTag(@Nullable String tag) {
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index dbc67fc..70811bb 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -1435,6 +1435,7 @@
      *                          of the companion device recorded by CompanionDeviceManager
      * @param tag the tag of this association
      */
+    @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
     @UserHandleAware
     public void setAssociationTag(int associationId, @NonNull String tag) {
         Objects.requireNonNull(tag, "tag cannot be null");
@@ -1459,6 +1460,7 @@
      *                          of the companion device recorded by CompanionDeviceManager
      * @see CompanionDeviceManager#setAssociationTag(int, String)
      */
+    @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
     @UserHandleAware
     public void clearAssociationTag(int associationId) {
         try {
diff --git a/core/java/android/companion/flags.aconfig b/core/java/android/companion/flags.aconfig
index 1b4234b..4f9c849 100644
--- a/core/java/android/companion/flags.aconfig
+++ b/core/java/android/companion/flags.aconfig
@@ -12,4 +12,11 @@
     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 b765562..bb9cc0b 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -64,6 +64,7 @@
 import android.os.ResultReceiver;
 import android.os.ShellCommand;
 import android.os.StrictMode;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
 import android.provider.ContactsContract.QuickContact;
@@ -2796,6 +2797,8 @@
      * started and is no longer considered stopped.
      * <ul>
      * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
+     * <li> {@link #EXTRA_TIME} containing the {@link SystemClock#elapsedRealtime()
+     *          elapsed realtime} of when the package was unstopped.
      * </ul>
      *
      * <p class="note">This is a protected intent that can only be sent by the system.
@@ -2869,9 +2872,15 @@
      *
      * <p class="note">This is a protected intent that can only be sent
      * by the system.
+     * <p>
+     * Starting in {@link Build.VERSION_CODES#VANILLA_ICE_CREAM Android V}, an extra timestamp
+     * {@link #EXTRA_TIME} is included with this broadcast to indicate the exact time the package
+     * was restarted, in {@link SystemClock#elapsedRealtime() elapsed realtime}.
+     * </p>
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_PACKAGE_RESTARTED = "android.intent.action.PACKAGE_RESTARTED";
+
     /**
      * Broadcast Action: The user has cleared the data of a package.  This should
      * be preceded by {@link #ACTION_PACKAGE_RESTARTED}, after which all of
@@ -6578,8 +6587,8 @@
             = "android.intent.extra.SHUTDOWN_USERSPACE_ONLY";
 
     /**
-     * Optional extra specifying a time in milliseconds since the Epoch. The value must be
-     * non-negative.
+     * Optional extra specifying a time in milliseconds. The timebase depends on the Intent
+     * including this extra. The value must be non-negative.
      * <p>
      * Type: long
      * </p>
diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
index c4547b8..df2d7e7 100644
--- a/core/java/android/content/om/FabricatedOverlay.java
+++ b/core/java/android/content/om/FabricatedOverlay.java
@@ -16,6 +16,7 @@
 
 package android.content.om;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -546,6 +547,7 @@
      * @param configuration The string representation of the config this overlay is enabled for
      */
     @NonNull
+    @FlaggedApi(android.content.res.Flags.FLAG_ASSET_FILE_DESCRIPTOR_FRRO)
     public void setResourceValue(
             @NonNull String resourceName,
             @NonNull AssetFileDescriptor value,
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 1b60f8e..b15c9e4 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1256,8 +1256,10 @@
     public static final long MATCH_ARCHIVED_PACKAGES = 1L << 32;
 
     /**
-     * @hide
+     * Querying flag: always match components of packages in quarantined state.
+     * @see #isPackageQuarantined
      */
+    @FlaggedApi(android.content.pm.Flags.FLAG_QUARANTINED_ENABLED)
     public static final long MATCH_QUARANTINED_COMPONENTS = 0x100000000L;
 
     /**
@@ -9902,12 +9904,16 @@
 
     /**
      * Query if an app is currently quarantined.
+     * A misbehaving app can be quarantined by e.g. a system of another privileged entity.
+     * Quarantined apps are similar to disabled, but still visible in e.g. Launcher.
+     * Only activities of such apps can still be queried, but not services etc.
+     * Quarantined apps can't be bound to, and won't receive broadcasts.
+     * They can't be resolved, unless {@link #MATCH_QUARANTINED_COMPONENTS} specified.
      *
      * @return {@code true} if the given package is quarantined, {@code false} otherwise
      * @throws NameNotFoundException if the package could not be found.
-     *
-     * @hide
      */
+    @FlaggedApi(android.content.pm.Flags.FLAG_QUARANTINED_ENABLED)
     public boolean isPackageQuarantined(@NonNull String packageName) throws NameNotFoundException {
         throw new UnsupportedOperationException("isPackageQuarantined not implemented");
     }
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 5cc3b92..c7790bd 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -27,6 +27,8 @@
 import android.annotation.RawRes;
 import android.annotation.StyleRes;
 import android.annotation.StyleableRes;
+import android.app.LocaleConfig;
+import android.app.ResourcesManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ActivityInfo.Config;
@@ -426,38 +428,59 @@
 
                 String[] selectedLocales = null;
                 String defaultLocale = null;
+                LocaleConfig lc = ResourcesManager.getInstance().getLocaleConfig();
                 if ((configChanges & ActivityInfo.CONFIG_LOCALE) != 0) {
                     if (locales.size() > 1) {
-                        String[] availableLocales;
-                        // The LocaleList has changed. We must query the AssetManager's
-                        // available Locales and figure out the best matching Locale in the new
-                        // LocaleList.
-                        availableLocales = mAssets.getNonSystemLocales();
-                        if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
-                            // No app defined locales, so grab the system locales.
-                            availableLocales = mAssets.getLocales();
-                            if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
-                                availableLocales = null;
+                        if (Flags.defaultLocale() && (lc.getDefaultLocale() != null)) {
+                            Locale[] intersection =
+                                    locales.getIntersection(lc.getSupportedLocales());
+                            mConfiguration.setLocales(new LocaleList(intersection));
+                            selectedLocales = new String[intersection.length];
+                            for (int i = 0; i < intersection.length; i++) {
+                                selectedLocales[i] =
+                                        adjustLanguageTag(intersection[i].toLanguageTag());
                             }
-                        }
+                            defaultLocale =
+                                    adjustLanguageTag(lc.getDefaultLocale().toLanguageTag());
+                        } else {
+                            String[] availableLocales;
+                            // The LocaleList has changed. We must query the AssetManager's
+                            // available Locales and figure out the best matching Locale in the new
+                            // LocaleList.
+                            availableLocales = mAssets.getNonSystemLocales();
+                            if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
+                                // No app defined locales, so grab the system locales.
+                                availableLocales = mAssets.getLocales();
+                                if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
+                                    availableLocales = null;
+                                }
+                            }
 
-                        if (availableLocales != null) {
-                            final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
-                                    availableLocales);
-                            if (bestLocale != null) {
-                                selectedLocales = new String[]{
-                                        adjustLanguageTag(bestLocale.toLanguageTag())};
-                                if (!bestLocale.equals(locales.get(0))) {
-                                    mConfiguration.setLocales(
-                                            new LocaleList(bestLocale, locales));
+                            if (availableLocales != null) {
+                                final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
+                                        availableLocales);
+                                if (bestLocale != null) {
+                                    selectedLocales = new String[]{
+                                            adjustLanguageTag(bestLocale.toLanguageTag())};
+                                    if (!bestLocale.equals(locales.get(0))) {
+                                        mConfiguration.setLocales(
+                                                new LocaleList(bestLocale, locales));
+                                    }
                                 }
                             }
                         }
                     }
                 }
                 if (selectedLocales == null) {
-                    selectedLocales = new String[]{
-                            adjustLanguageTag(locales.get(0).toLanguageTag())};
+                    if (Flags.defaultLocale() && (lc.getDefaultLocale() != null)) {
+                        selectedLocales = new String[locales.size()];
+                        for (int i = 0; i < locales.size(); i++) {
+                            selectedLocales[i] = adjustLanguageTag(locales.get(i).toLanguageTag());
+                        }
+                    } else {
+                        selectedLocales = new String[]{
+                                adjustLanguageTag(locales.get(0).toLanguageTag())};
+                    }
                 }
 
                 if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) {
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index 0c2c0f4..1b8eb07 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -8,3 +8,10 @@
     # fixed_read_only or device wont boot because of permission issues accessing flags during boot
     is_fixed_read_only: true
 }
+
+flag {
+    name: "asset_file_descriptor_frro"
+    namespace: "resource_manager"
+    description: "Feature flag for passing in an AssetFileDescriptor to create an frro"
+    bug: "304478666"
+}
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index b003e75..8a4f678 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -701,7 +701,7 @@
      *   }
      * </pre>
      */
-    @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
+    @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
     public void beginTransactionReadOnly() {
         beginTransactionWithListenerReadOnly(null);
     }
@@ -785,7 +785,7 @@
      *   }
      * </pre>
      */
-    @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
+    @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
     public void beginTransactionWithListenerReadOnly(
             @Nullable SQLiteTransactionListener transactionListener) {
         beginTransaction(transactionListener, SQLiteSession.TRANSACTION_MODE_DEFERRED);
@@ -2224,7 +2224,7 @@
      * @throws IllegalStateException if a transaction is not in progress.
      * @throws SQLiteException if the SQL cannot be compiled.
      */
-    @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
+    @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
     @NonNull
     public SQLiteRawStatement createRawStatement(@NonNull String sql) {
         Objects.requireNonNull(sql);
@@ -2244,7 +2244,7 @@
      * @return The ROWID of the last row to be inserted under this connection.
      * @throws IllegalStateException if there is no current transaction.
      */
-    @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
+    @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
     public long getLastInsertRowId() {
         return getThreadSession().getLastInsertRowId();
     }
@@ -2258,7 +2258,7 @@
      * @return The number of rows changed by the most recent sql statement
      * @throws IllegalStateException if there is no current transaction.
      */
-    @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
+    @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
     public long getLastChangedRowCount() {
         return getThreadSession().getLastChangedRowCount();
     }
@@ -2286,7 +2286,7 @@
      * @return The number of rows changed on the current connection.
      * @throws IllegalStateException if there is no current transaction.
      */
-    @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
+    @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
     public long getTotalChangedRowCount() {
         return getThreadSession().getTotalChangedRowCount();
     }
diff --git a/core/java/android/database/sqlite/SQLiteRawStatement.java b/core/java/android/database/sqlite/SQLiteRawStatement.java
index 827420f..33f602b 100644
--- a/core/java/android/database/sqlite/SQLiteRawStatement.java
+++ b/core/java/android/database/sqlite/SQLiteRawStatement.java
@@ -71,7 +71,7 @@
  *
  * @see <a href="http://sqlite.org/c3ref/stmt.html">sqlite3_stmt</a>
  */
-@FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
+@FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
 public final class SQLiteRawStatement implements Closeable {
 
     private static final String TAG = "SQLiteRawStatement";
diff --git a/core/java/android/database/sqlite/flags.aconfig b/core/java/android/database/sqlite/flags.aconfig
index 564df03..62a5123 100644
--- a/core/java/android/database/sqlite/flags.aconfig
+++ b/core/java/android/database/sqlite/flags.aconfig
@@ -1,7 +1,7 @@
 package: "android.database.sqlite"
 
 flag {
-     name: "sqlite_apis_15"
+     name: "sqlite_apis_35"
      namespace: "system_performance"
      is_fixed_read_only: true
      description: "SQLite APIs held back for Android 15"
diff --git a/core/java/android/hardware/HardwareBuffer.aidl b/core/java/android/hardware/HardwareBuffer.aidl
index 1333f0d..a9742cb 100644
--- a/core/java/android/hardware/HardwareBuffer.aidl
+++ b/core/java/android/hardware/HardwareBuffer.aidl
@@ -16,4 +16,4 @@
 
 package android.hardware;
 
-@JavaOnlyStableParcelable @NdkOnlyStableParcelable parcelable HardwareBuffer ndk_header "android/hardware_buffer_aidl.h";
+@JavaOnlyStableParcelable @NdkOnlyStableParcelable @RustOnlyStableParcelable parcelable HardwareBuffer ndk_header "android/hardware_buffer_aidl.h" rust_type "nativewindow::HardwareBuffer";
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 490ff64..7a43286 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -805,7 +805,7 @@
          * Get {@link Signature} object.
          * @return {@link Signature} object or null if this doesn't contain one.
          */
-        public Signature getSignature() {
+        public @Nullable Signature getSignature() {
             return super.getSignature();
         }
 
@@ -813,7 +813,7 @@
          * Get {@link Cipher} object.
          * @return {@link Cipher} object or null if this doesn't contain one.
          */
-        public Cipher getCipher() {
+        public @Nullable Cipher getCipher() {
             return super.getCipher();
         }
 
@@ -821,7 +821,7 @@
          * Get {@link Mac} object.
          * @return {@link Mac} object or null if this doesn't contain one.
          */
-        public Mac getMac() {
+        public @Nullable Mac getMac() {
             return super.getMac();
         }
 
diff --git a/core/java/android/hardware/biometrics/CryptoObject.java b/core/java/android/hardware/biometrics/CryptoObject.java
index 6ac1efb..39fbe83 100644
--- a/core/java/android/hardware/biometrics/CryptoObject.java
+++ b/core/java/android/hardware/biometrics/CryptoObject.java
@@ -20,6 +20,7 @@
 
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.security.identity.IdentityCredential;
 import android.security.identity.PresentationSession;
 import android.security.keystore2.AndroidKeyStoreProvider;
@@ -33,20 +34,35 @@
 /**
  * A wrapper class for the crypto objects supported by BiometricPrompt and FingerprintManager.
  * Currently the framework supports {@link Signature}, {@link Cipher}, {@link Mac},
- * {@link IdentityCredential}, and {@link PresentationSession} objects.
+ * {@link KeyAgreement}, {@link IdentityCredential}, and {@link PresentationSession} objects.
  * @hide
  */
 public class CryptoObject {
     private final Object mCrypto;
 
+    /**
+     * Create from a {@link Signature} object.
+     *
+     * @param signature a {@link Signature} object.
+     */
     public CryptoObject(@NonNull Signature signature) {
         mCrypto = signature;
     }
 
+    /**
+     * Create from a {@link Cipher} object.
+     *
+     * @param cipher a {@link Cipher} object.
+     */
     public CryptoObject(@NonNull Cipher cipher) {
         mCrypto = cipher;
     }
 
+    /**
+     * Create from a {@link Mac} object.
+     *
+     * @param mac a {@link Mac} object.
+     */
     public CryptoObject(@NonNull Mac mac) {
         mCrypto = mac;
     }
@@ -62,10 +78,20 @@
         mCrypto = credential;
     }
 
+    /**
+     * Create from a {@link PresentationSession} object.
+     *
+     * @param session a {@link PresentationSession} object.
+     */
     public CryptoObject(@NonNull PresentationSession session) {
         mCrypto = session;
     }
 
+    /**
+     * Create from a {@link KeyAgreement} object.
+     *
+     * @param keyAgreement a {@link KeyAgreement} object.
+     */
     @FlaggedApi(FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT)
     public CryptoObject(@NonNull KeyAgreement keyAgreement) {
         mCrypto = keyAgreement;
@@ -75,7 +101,7 @@
      * Get {@link Signature} object.
      * @return {@link Signature} object or null if this doesn't contain one.
      */
-    public Signature getSignature() {
+    public @Nullable Signature getSignature() {
         return mCrypto instanceof Signature ? (Signature) mCrypto : null;
     }
 
@@ -83,7 +109,7 @@
      * Get {@link Cipher} object.
      * @return {@link Cipher} object or null if this doesn't contain one.
      */
-    public Cipher getCipher() {
+    public @Nullable Cipher getCipher() {
         return mCrypto instanceof Cipher ? (Cipher) mCrypto : null;
     }
 
@@ -91,7 +117,7 @@
      * Get {@link Mac} object.
      * @return {@link Mac} object or null if this doesn't contain one.
      */
-    public Mac getMac() {
+    public @Nullable Mac getMac() {
         return mCrypto instanceof Mac ? (Mac) mCrypto : null;
     }
 
@@ -101,7 +127,7 @@
      * @deprecated Use {@link PresentationSession} instead of {@link IdentityCredential}.
      */
     @Deprecated
-    public IdentityCredential getIdentityCredential() {
+    public @Nullable IdentityCredential getIdentityCredential() {
         return mCrypto instanceof IdentityCredential ? (IdentityCredential) mCrypto : null;
     }
 
@@ -109,16 +135,18 @@
      * Get {@link PresentationSession} object.
      * @return {@link PresentationSession} object or null if this doesn't contain one.
      */
-    public PresentationSession getPresentationSession() {
+    public @Nullable PresentationSession getPresentationSession() {
         return mCrypto instanceof PresentationSession ? (PresentationSession) mCrypto : null;
     }
 
     /**
-     * Get {@link KeyAgreement} object.
+     * Get {@link KeyAgreement} object. A key-agreement protocol is a protocol whereby
+     * two or more parties can agree on a shared secret using public key cryptography.
+     *
      * @return {@link KeyAgreement} object or null if this doesn't contain one.
      */
     @FlaggedApi(FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT)
-    public KeyAgreement getKeyAgreement() {
+    public @Nullable KeyAgreement getKeyAgreement() {
         return mCrypto instanceof KeyAgreement ? (KeyAgreement) mCrypto : null;
     }
 
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 8decd50..2b5f5ee 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -220,7 +220,7 @@
 
         registerCallbackIfNeededLocked();
 
-        if (DEBUG || extraLogging()) {
+        if (DEBUG) {
             Log.d(TAG, "getDisplayInfo: displayId=" + displayId + ", info=" + info);
         }
         return info;
@@ -402,7 +402,7 @@
     }
 
     private void maybeLogAllDisplayListeners() {
-        if (!sExtraDisplayListenerLogging) {
+        if (!extraLogging()) {
             return;
         }
 
@@ -1222,7 +1222,7 @@
 
         private void handleMessage(Message msg) {
             if (extraLogging()) {
-                Slog.i(TAG, "DisplayListenerDelegate(" + eventToString(msg.what)
+                Slog.i(TAG, "DLD(" + eventToString(msg.what)
                         + ", display=" + msg.arg1
                         + ", mEventsMask=" + Long.toBinaryString(mEventsMask)
                         + ", mPackageName=" + mPackageName
@@ -1231,9 +1231,10 @@
             }
             if (DEBUG) {
                 Trace.beginSection(
-                        "DisplayListenerDelegate(" + eventToString(msg.what)
+                        TextUtils.trimToSize(
+                                "DLD(" + eventToString(msg.what)
                                 + ", display=" + msg.arg1
-                                + ", listener=" + mListener.getClass() + ")");
+                                + ", listener=" + mListener.getClass() + ")", 127));
             }
             switch (msg.what) {
                 case EVENT_DISPLAY_ADDED:
@@ -1422,11 +1423,12 @@
             sExtraDisplayListenerLogging = !TextUtils.isEmpty(EXTRA_LOGGING_PACKAGE_NAME)
                     && EXTRA_LOGGING_PACKAGE_NAME.equals(sCurrentPackageName);
         }
-        return sExtraDisplayListenerLogging;
+        // TODO: b/306170135 - return sExtraDisplayListenerLogging instead
+        return true;
     }
 
     private static boolean extraLogging() {
-        return sExtraDisplayListenerLogging && EXTRA_LOGGING_PACKAGE_NAME.equals(
-                sCurrentPackageName);
+        // TODO: b/306170135 - return sExtraDisplayListenerLogging & package name check instead
+        return true;
     }
 }
diff --git a/core/java/android/nfc/Constants.java b/core/java/android/nfc/Constants.java
new file mode 100644
index 0000000..f768330
--- /dev/null
+++ b/core/java/android/nfc/Constants.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc;
+
+/**
+ * @hide
+ * TODO(b/303286040): Holds @hide API constants. Formalize these APIs.
+ */
+public final class Constants {
+    private Constants() { }
+
+    public static final String SETTINGS_SECURE_NFC_PAYMENT_FOREGROUND = "nfc_payment_foreground";
+    public static final String SETTINGS_SECURE_NFC_PAYMENT_DEFAULT_COMPONENT = "nfc_payment_default_component";
+    public static final String FEATURE_NFC_ANY = "android.hardware.nfc.any";
+}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 4658630..4a7bd3f 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -24,6 +24,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.UserIdInt;
 import android.app.Activity;
@@ -37,6 +38,7 @@
 import android.nfc.tech.Ndef;
 import android.nfc.tech.NfcA;
 import android.nfc.tech.NfcF;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
@@ -1594,6 +1596,40 @@
         mNfcActivityManager.disableReaderMode(activity);
     }
 
+    // Flags arguments to NFC adapter to enable/disable NFC
+    private static final int DISABLE_POLLING_FLAGS = 0x1000;
+    private static final int ENABLE_POLLING_FLAGS = 0x0000;
+
+    /**
+     * Privileged API to enable disable reader polling.
+     * Note: Use with caution! The app is responsible for ensuring that the polling state is
+     * returned to normal.
+     *
+     * @see #enableReaderMode(Activity, ReaderCallback, int, Bundle)  for more detailed
+     * documentation.
+     *
+     * @param enablePolling whether to enable or disable polling.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @SuppressLint("VisiblySynchronized")
+    public void setReaderMode(boolean enablePolling) {
+        synchronized (NfcAdapter.class) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
+        Binder token = new Binder();
+        int flags = enablePolling ? ENABLE_POLLING_FLAGS : DISABLE_POLLING_FLAGS;
+        try {
+            NfcAdapter.sService.setReaderMode(token, null, flags, null);
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+        }
+    }
+
     /**
      * Manually invoke Android Beam to share data.
      *
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
index 4909b08..32c2a1b 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -26,6 +26,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.nfc.Constants;
 import android.nfc.INfcCardEmulation;
 import android.nfc.NfcAdapter;
 import android.os.RemoteException;
@@ -274,7 +275,7 @@
             try {
                 preferForeground = Settings.Secure.getInt(
                         contextAsUser.getContentResolver(),
-                        Settings.Secure.NFC_PAYMENT_FOREGROUND) != 0;
+                        Constants.SETTINGS_SECURE_NFC_PAYMENT_FOREGROUND) != 0;
             } catch (SettingNotFoundException e) {
             }
             return preferForeground;
diff --git a/core/java/android/os/BugreportParams.java b/core/java/android/os/BugreportParams.java
index 47ad72f..e8ad303 100644
--- a/core/java/android/os/BugreportParams.java
+++ b/core/java/android/os/BugreportParams.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
@@ -133,6 +134,7 @@
      * The maximum value of supported bugreport mode.
      * @hide
      */
+    @FlaggedApi(android.os.Flags.FLAG_BUGREPORT_MODE_MAX_VALUE)
     @TestApi
     public static final int BUGREPORT_MODE_MAX_VALUE = BUGREPORT_MODE_ONBOARDING;
 
diff --git a/core/java/android/os/LocaleList.java b/core/java/android/os/LocaleList.java
index 82cdd28..d7e440b 100644
--- a/core/java/android/os/LocaleList.java
+++ b/core/java/android/os/LocaleList.java
@@ -153,21 +153,21 @@
 
     /**
      * Find the intersection between this LocaleList and another
-     * @return a String array of the Locales in both LocaleLists
+     * @return an array of the Locales in both LocaleLists
      * {@hide}
      */
     @NonNull
-    public String[] getIntersection(@NonNull LocaleList other) {
-        List<String> intersection = new ArrayList<>();
+    public Locale[] getIntersection(@NonNull LocaleList other) {
+        List<Locale> intersection = new ArrayList<>();
         for (Locale l1 : mList) {
             for (Locale l2 : other.mList) {
                 if (matchesLanguageAndScript(l2, l1)) {
-                    intersection.add(l1.toLanguageTag());
+                    intersection.add(l1);
                     break;
                 }
             }
         }
-        return intersection.toArray(new String[0]);
+        return intersection.toArray(new Locale[0]);
     }
 
     /**
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index b7f2e06..c4521c0 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -27,3 +27,10 @@
     description: "Guards a new Private Profile type in UserManager - everything from its setup to config to deletion."
     bug: "299069460"
 }
+
+flag {
+    name: "bugreport_mode_max_value"
+    namespace: "telephony"
+    description: "Introduce a constant as maximum value of bugreport mode."
+    bug: "305067125"
+}
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index c82a4ca..2a4cbaf 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -28,8 +28,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
-
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.List;
@@ -59,8 +57,7 @@
      * @hide
      */
     public NotificationRankingUpdate(Parcel in) {
-        if (SystemUiSystemPropertiesFlags.getResolver().isEnabled(
-                SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) {
+        if (Flags.rankingUpdateAshmem()) {
             // Recover the ranking map from the SharedMemory and store it in mapParcel.
             final Parcel mapParcel = Parcel.obtain();
             ByteBuffer buffer = null;
@@ -176,8 +173,7 @@
      */
     @Override
     public void writeToParcel(@NonNull Parcel out, int flags) {
-        if (SystemUiSystemPropertiesFlags.getResolver().isEnabled(
-                SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) {
+        if (Flags.rankingUpdateAshmem()) {
             final Parcel mapParcel = Parcel.obtain();
             ArrayList<NotificationListenerService.Ranking> marshalableRankings = new ArrayList<>();
             Bundle smartActionsBundle = new Bundle();
diff --git a/core/java/android/service/notification/flags.aconfig b/core/java/android/service/notification/flags.aconfig
new file mode 100644
index 0000000..2931435
--- /dev/null
+++ b/core/java/android/service/notification/flags.aconfig
@@ -0,0 +1,9 @@
+package: "android.service.notification"
+
+flag {
+  name: "ranking_update_ashmem"
+  namespace: "systemui"
+  description: "This flag controls moving ranking update contents into ashmem"
+  bug: "284297289"
+}
+
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 7cee4cb..a7b3c10 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1055,7 +1055,8 @@
         mDisplay = display;
         mBasePackageName = context.getBasePackageName();
         final String name = DisplayProperties.debug_vri_package().orElse(null);
-        mExtraDisplayListenerLogging = !TextUtils.isEmpty(name) && name.equals(mBasePackageName);
+        // TODO: b/306170135 - return to using textutils check on package name.
+        mExtraDisplayListenerLogging = true;
         mThread = Thread.currentThread();
         mLocation = new WindowLeaked(null);
         mLocation.fillInStackTrace();
@@ -2049,6 +2050,10 @@
                     Slog.i(mTag, "DisplayState - old: " + oldDisplayState
                             + ", new: " + newDisplayState);
                 }
+                if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+                    Trace.traceCounter(Trace.TRACE_TAG_WINDOW_MANAGER,
+                            "vri#screenState[" + mTag + "] state=", newDisplayState);
+                }
                 if (oldDisplayState != newDisplayState) {
                     mAttachInfo.mDisplayState = newDisplayState;
                     pokeDrawLockIfNeeded();
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index cc612ed..6888b50 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -1,10 +1,12 @@
 package: "android.view.accessibility"
 
+# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
+
 flag {
+    name: "a11y_overlay_callbacks"
     namespace: "accessibility"
-    name: "force_invert_color"
-    description: "Enable force force-dark for smart inversion and dark theme everywhere"
-    bug: "282821643"
+    description: "Whether to allow the passing of result callbacks when attaching a11y overlays."
+    bug: "304478691"
 }
 
 flag {
@@ -15,8 +17,8 @@
 }
 
 flag {
-        name: "a11y_overlay_callbacks"
     namespace: "accessibility"
-    description: "Whether to allow the passing of result callbacks when attaching a11y overlays."
-    bug: "304478691"
+    name: "force_invert_color"
+    description: "Enable force force-dark for smart inversion and dark theme everywhere"
+    bug: "282821643"
 }
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 103725d..f19a2f9 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -685,8 +685,9 @@
             return false;
         }
 
+        /** See {@link RemoteViews#visitUris(Consumer)}. **/
         public void visitUris(@NonNull Consumer<Uri> visitor) {
-            // Nothing to visit by default
+            // Nothing to visit by default.
         }
     }
 
@@ -761,9 +762,11 @@
     }
 
     /**
-     * Note all {@link Uri} that are referenced internally, with the expectation
-     * that Uri permission grants will need to be issued to ensure the recipient
-     * of this object is able to render its contents.
+     * Note all {@link Uri} that are referenced internally, with the expectation that Uri permission
+     * grants will need to be issued to ensure the recipient of this object is able to render its
+     * contents.
+     * See b/281044385 for more context and examples about what happens when this isn't done
+     * correctly.
      *
      * @hide
      */
@@ -1088,6 +1091,13 @@
         public String getUniqueKey() {
             return (SET_REMOTE_ADAPTER_TAG + "_" + mViewId);
         }
+
+        @Override
+        public void visitUris(@NonNull Consumer<Uri> visitor) {
+            for (RemoteViews remoteViews : mList) {
+                remoteViews.visitUris(visitor);
+            }
+        }
     }
 
     /**
@@ -1289,6 +1299,12 @@
         public String getUniqueKey() {
             return (SET_REMOTE_ADAPTER_TAG + "_" + mViewId);
         }
+
+        @Override
+        public void visitUris(@NonNull Consumer<Uri> visitor) {
+            RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture);
+            items.visitUris(visitor);
+        }
     }
 
     private class SetRemoteViewsAdapterIntent extends Action {
@@ -1359,6 +1375,13 @@
         public int getActionTag() {
             return SET_REMOTE_VIEW_ADAPTER_INTENT_TAG;
         }
+
+        @Override
+        public void visitUris(@NonNull Consumer<Uri> visitor) {
+            // TODO(b/281044385): Maybe visit intent URIs. This may require adding a dedicated
+            //  visitUris method in the Intent class, since it can contain other intents. Otherwise,
+            //  the basic thing to do here would be just visitor.accept(intent.getData()).
+        }
     }
 
     /**
@@ -1434,6 +1457,11 @@
         public int getActionTag() {
             return SET_ON_CLICK_RESPONSE_TAG;
         }
+
+        @Override
+        public void visitUris(@NonNull Consumer<Uri> visitor) {
+            // TODO(b/281044385): Maybe visit intent URIs in the RemoteResponse.
+        }
     }
 
     /**
@@ -1504,6 +1532,11 @@
         public int getActionTag() {
             return SET_ON_CHECKED_CHANGE_RESPONSE_TAG;
         }
+
+        @Override
+        public void visitUris(@NonNull Consumer<Uri> visitor) {
+            // TODO(b/281044385): Maybe visit intent URIs in the RemoteResponse.
+        }
     }
 
     /** @hide **/
@@ -2063,6 +2096,7 @@
                     final Icon icon = (Icon) getParameterValue(null);
                     if (icon != null) visitIconUri(icon, visitor);
                     break;
+                // TODO(b/281044385): Should we do anything about type BUNDLE?
             }
         }
     }
@@ -2812,7 +2846,7 @@
         }
 
         @Override
-        public final void visitUris(@NonNull Consumer<Uri> visitor) {
+        public void visitUris(@NonNull Consumer<Uri> visitor) {
             mNestedViews.visitUris(visitor);
         }
     }
@@ -7262,6 +7296,15 @@
                         Math.max(mViewTypeCount, 1));
             }
         }
+
+        /**
+         * See {@link RemoteViews#visitUris(Consumer)}.
+         */
+        private void visitUris(@NonNull Consumer<Uri> visitor) {
+            for (RemoteViews view : mViews) {
+                view.visitUris(visitor);
+            }
+        }
     }
 
     /**
diff --git a/core/java/com/android/internal/app/PlatLogoActivity.java b/core/java/com/android/internal/app/PlatLogoActivity.java
index 4e7bfe5..71bbccb 100644
--- a/core/java/com/android/internal/app/PlatLogoActivity.java
+++ b/core/java/com/android/internal/app/PlatLogoActivity.java
@@ -259,7 +259,7 @@
             }
             return true;
         }
-        return false;
+        return super.onKeyDown(keyCode,event);
     }
 
     @Override
@@ -268,7 +268,7 @@
             stopWarp();
             return true;
         }
-        return false;
+        return super.onKeyUp(keyCode,event);
     }
 
     private void startWarp() {
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 6e836e0..7e9cef7 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -40,6 +40,7 @@
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNFOLD_ANIM;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_LAUNCH_CAMERA;
@@ -273,7 +274,9 @@
 
     public static final int CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER = 82;
 
-    private static final int LAST_CUJ = CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
+    public static final int CUJ_LAUNCHER_UNFOLD_ANIM = 83;
+
+    private static final int LAST_CUJ = CUJ_LAUNCHER_UNFOLD_ANIM;
     private static final int NO_STATSD_LOGGING = -1;
 
     // Used to convert CujType to InteractionType enum value for statsd logging.
@@ -366,6 +369,7 @@
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_SHOW_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_SHOW_ANIMATION;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_HIDE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_HIDE_ANIMATION;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_UNFOLD_ANIM] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNFOLD_ANIM;
     }
 
     private static class InstanceHolder {
@@ -468,6 +472,7 @@
             CUJ_IME_INSETS_SHOW_ANIMATION,
             CUJ_IME_INSETS_HIDE_ANIMATION,
             CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER,
+            CUJ_LAUNCHER_UNFOLD_ANIM,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {
@@ -1101,6 +1106,8 @@
                 return "IME_INSETS_HIDE_ANIMATION";
             case CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER:
                 return "SPLIT_SCREEN_DOUBLE_TAP_DIVIDER";
+            case CUJ_LAUNCHER_UNFOLD_ANIM:
+                return "LAUNCHER_UNFOLD_ANIM";
         }
         return "UNKNOWN";
     }
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 04fd70a..3496994 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -10090,6 +10090,15 @@
     <!-- Perceptual luminance of a color, in accessibility friendly color space. From 0 to 100. -->
     <attr name="lStar" format="float"/>
 
+    <!-- The attributes of the {@code <locale-config>} tag. -->
+    <!-- @FlaggedApi("android.content.res.default_locale") -->
+    <declare-styleable name="LocaleConfig">
+        <!-- The <a href="https://www.rfc-editor.org/rfc/bcp/bcp47.txt">IETF BCP47 language tag</a>
+       the strings in values/strings.xml (the default strings in the directory with no locale
+       qualifier) are in. -->
+        <attr name="defaultLocale" format="string"/>
+    </declare-styleable>
+
     <!-- The attributes of the {@code <locale>} tag within {@code <locale-config>}. -->
     <declare-styleable name="LocaleConfig_Locale">
         <!-- The <a href="https://www.rfc-editor.org/rfc/bcp/bcp47.txt">IETF BCP47 language tag</a>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index adc7fe0..4cd4f63 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -110,6 +110,8 @@
   <eat-comment/>
 
   <staging-public-group type="attr" first-id="0x01bd0000">
+    <!-- @FlaggedApi("android.content.res.default_locale") -->
+    <public name="defaultLocale"/>
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01bc0000">
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
index 531404b..d10cf16 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
@@ -62,4 +62,24 @@
         verify(callback2, times(1)).preExecute(clientTransactionHandler);
         verify(stateRequest, times(1)).preExecute(clientTransactionHandler);
     }
+
+    @Test
+    public void testPreExecuteTransactionItems() {
+        final ClientTransactionItem callback1 = mock(ClientTransactionItem.class);
+        final ClientTransactionItem callback2 = mock(ClientTransactionItem.class);
+        final ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
+        final ClientTransactionHandler clientTransactionHandler =
+                mock(ClientTransactionHandler.class);
+
+        final ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
+        transaction.addTransactionItem(callback1);
+        transaction.addTransactionItem(callback2);
+        transaction.addTransactionItem(stateRequest);
+
+        transaction.preExecute(clientTransactionHandler);
+
+        verify(callback1, times(1)).preExecute(clientTransactionHandler);
+        verify(callback2, times(1)).preExecute(clientTransactionHandler);
+        verify(stateRequest, times(1)).preExecute(clientTransactionHandler);
+    }
 }
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index 44a4d58..f2b0f2e 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -247,6 +247,31 @@
     }
 
     @Test
+    public void testExecuteTransactionItems_transactionResolution() {
+        ClientTransactionItem callback1 = mock(ClientTransactionItem.class);
+        when(callback1.getPostExecutionState()).thenReturn(UNDEFINED);
+        ClientTransactionItem callback2 = mock(ClientTransactionItem.class);
+        when(callback2.getPostExecutionState()).thenReturn(UNDEFINED);
+        ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
+        IBinder token = mock(IBinder.class);
+        when(stateRequest.getActivityToken()).thenReturn(token);
+        when(mTransactionHandler.getActivity(token)).thenReturn(mock(Activity.class));
+
+        ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
+        transaction.addTransactionItem(callback1);
+        transaction.addTransactionItem(callback2);
+        transaction.addTransactionItem(stateRequest);
+
+        transaction.preExecute(mTransactionHandler);
+        mExecutor.execute(transaction);
+
+        InOrder inOrder = inOrder(mTransactionHandler, callback1, callback2, stateRequest);
+        inOrder.verify(callback1).execute(eq(mTransactionHandler), any());
+        inOrder.verify(callback2).execute(eq(mTransactionHandler), any());
+        inOrder.verify(stateRequest).execute(eq(mTransactionHandler), eq(mClientRecord), any());
+    }
+
+    @Test
     public void testDoNotLaunchDestroyedActivity() {
         final Map<IBinder, DestroyActivityItem> activitiesToBeDestroyed = new ArrayMap<>();
         when(mTransactionHandler.getActivitiesToBeDestroyed()).thenReturn(activitiesToBeDestroyed);
@@ -279,12 +304,43 @@
     }
 
     @Test
+    public void testExecuteTransactionItems_doNotLaunchDestroyedActivity() {
+        final Map<IBinder, DestroyActivityItem> activitiesToBeDestroyed = new ArrayMap<>();
+        when(mTransactionHandler.getActivitiesToBeDestroyed()).thenReturn(activitiesToBeDestroyed);
+        // Assume launch transaction is still in queue, so there is no client record.
+        when(mTransactionHandler.getActivityClient(any())).thenReturn(null);
+
+        // An incoming destroy transaction enters binder thread (preExecute).
+        final IBinder token = mock(IBinder.class);
+        final ClientTransaction destroyTransaction = ClientTransaction.obtain(null /* client */);
+        destroyTransaction.addTransactionItem(
+                DestroyActivityItem.obtain(token, false /* finished */, 0 /* configChanges */));
+        destroyTransaction.preExecute(mTransactionHandler);
+        // The activity should be added to to-be-destroyed container.
+        assertEquals(1, activitiesToBeDestroyed.size());
+
+        // A previous queued launch transaction runs on main thread (execute).
+        final ClientTransaction launchTransaction = ClientTransaction.obtain(null /* client */);
+        final LaunchActivityItem launchItem =
+                spy(new LaunchActivityItemBuilder().setActivityToken(token).build());
+        launchTransaction.addTransactionItem(launchItem);
+        mExecutor.execute(launchTransaction);
+
+        // The launch transaction should not be executed because its token is in the
+        // to-be-destroyed container.
+        verify(launchItem, never()).execute(any(), any());
+
+        // After the destroy transaction has been executed, the token should be removed.
+        mExecutor.execute(destroyTransaction);
+        assertTrue(activitiesToBeDestroyed.isEmpty());
+    }
+
+    @Test
     public void testActivityResultRequiredStateResolution() {
         when(mTransactionHandler.getActivity(any())).thenReturn(mock(Activity.class));
 
         PostExecItem postExecItem = new PostExecItem(ON_RESUME);
 
-        IBinder token = mock(IBinder.class);
         ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
         transaction.addCallback(postExecItem);
 
@@ -300,6 +356,26 @@
     }
 
     @Test
+    public void testExecuteTransactionItems_activityResultRequiredStateResolution() {
+        when(mTransactionHandler.getActivity(any())).thenReturn(mock(Activity.class));
+
+        PostExecItem postExecItem = new PostExecItem(ON_RESUME);
+
+        ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
+        transaction.addTransactionItem(postExecItem);
+
+        // Verify resolution that should get to onPause
+        mClientRecord.setState(ON_RESUME);
+        mExecutor.executeTransactionItems(transaction);
+        verify(mExecutor).cycleToPath(eq(mClientRecord), eq(ON_PAUSE), eq(transaction));
+
+        // Verify resolution that should get to onStart
+        mClientRecord.setState(ON_STOP);
+        mExecutor.executeTransactionItems(transaction);
+        verify(mExecutor).cycleToPath(eq(mClientRecord), eq(ON_START), eq(transaction));
+    }
+
+    @Test
     public void testClosestStateResolutionForSameState() {
         final int[] allStates = new int[] {
                 ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY};
@@ -444,6 +520,18 @@
         mExecutor.executeCallbacks(transaction);
     }
 
+    @Test(expected = IllegalArgumentException.class)
+    public void testExecuteTransactionItems_activityItemNullRecordThrowsException() {
+        final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class);
+        when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED);
+        final IBinder token = mock(IBinder.class);
+        final ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
+        transaction.addTransactionItem(activityItem);
+        when(mTransactionHandler.getActivityClient(token)).thenReturn(null);
+
+        mExecutor.executeTransactionItems(transaction);
+    }
+
     @Test
     public void testActivityItemExecute() {
         final IBinder token = mock(IBinder.class);
@@ -464,6 +552,26 @@
         inOrder.verify(stateRequest).execute(eq(mTransactionHandler), eq(mClientRecord), any());
     }
 
+    @Test
+    public void testExecuteTransactionItems_activityItemExecute() {
+        final IBinder token = mock(IBinder.class);
+        final ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
+        final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class);
+        when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED);
+        when(activityItem.getActivityToken()).thenReturn(token);
+        transaction.addTransactionItem(activityItem);
+        final ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
+        transaction.addTransactionItem(stateRequest);
+        when(stateRequest.getActivityToken()).thenReturn(token);
+        when(mTransactionHandler.getActivity(token)).thenReturn(mock(Activity.class));
+
+        mExecutor.execute(transaction);
+
+        final InOrder inOrder = inOrder(activityItem, stateRequest);
+        inOrder.verify(activityItem).execute(eq(mTransactionHandler), eq(mClientRecord), any());
+        inOrder.verify(stateRequest).execute(eq(mTransactionHandler), eq(mClientRecord), any());
+    }
+
     private static int[] shuffledArray(int[] inputArray) {
         final List<Integer> list = Arrays.stream(inputArray).boxed().collect(Collectors.toList());
         Collections.shuffle(list);
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 7d047c9..4aa62c5 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -285,6 +285,10 @@
                 78 /* configChanges */);
 
         ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
+        transaction.addTransactionItem(callback1);
+        transaction.addTransactionItem(callback2);
+        transaction.addTransactionItem(lifecycleRequest);
+
         transaction.addCallback(callback1);
         transaction.addCallback(callback2);
         transaction.setLifecycleStateRequest(lifecycleRequest);
diff --git a/core/tests/coretests/src/android/os/LocaleListTest.java b/core/tests/coretests/src/android/os/LocaleListTest.java
index 1f00a7a..88fc826 100644
--- a/core/tests/coretests/src/android/os/LocaleListTest.java
+++ b/core/tests/coretests/src/android/os/LocaleListTest.java
@@ -81,4 +81,49 @@
         // restore the original values
         LocaleList.setDefault(originalLocaleList, originalLocaleIndex);
     }
+
+    @SmallTest
+    public void testIntersection() {
+        LocaleList localesWithN = new LocaleList(
+                Locale.ENGLISH,
+                Locale.FRENCH,
+                Locale.GERMAN,
+                Locale.ITALIAN,
+                Locale.JAPANESE,
+                Locale.KOREAN,
+                Locale.CHINESE,
+                Locale.SIMPLIFIED_CHINESE,
+                Locale.TRADITIONAL_CHINESE,
+                Locale.FRANCE,
+                Locale.GERMANY,
+                Locale.JAPAN,
+                Locale.CANADA,
+                Locale.CANADA_FRENCH);
+        LocaleList localesWithE = new LocaleList(
+                Locale.ENGLISH,
+                Locale.FRENCH,
+                Locale.GERMAN,
+                Locale.JAPANESE,
+                Locale.KOREAN,
+                Locale.CHINESE,
+                Locale.SIMPLIFIED_CHINESE,
+                Locale.TRADITIONAL_CHINESE,
+                Locale.FRANCE,
+                Locale.GERMANY,
+                Locale.CANADA_FRENCH);
+        LocaleList localesWithNAndE = new LocaleList(
+                Locale.ENGLISH,
+                Locale.FRENCH,
+                Locale.GERMAN,
+                Locale.JAPANESE,
+                Locale.KOREAN,
+                Locale.CHINESE,
+                Locale.SIMPLIFIED_CHINESE,
+                Locale.TRADITIONAL_CHINESE,
+                Locale.FRANCE,
+                Locale.GERMANY,
+                Locale.CANADA_FRENCH);
+
+        assertEquals(localesWithNAndE, new LocaleList(localesWithE.getIntersection(localesWithN)));
+    }
 }
diff --git a/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
index 517aeae..0855268 100644
--- a/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
+++ b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
@@ -20,8 +20,6 @@
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE;
 
-import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM;
-
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
@@ -42,16 +40,12 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.SharedMemory;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.TestableContext;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.Flag;
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.FlagResolver;
-
-import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
@@ -71,6 +65,9 @@
 
     private NotificationChannel mNotificationChannel;
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     // TODO(b/284297289): remove this flag set once resolved.
     @Parameterized.Parameters(name = "rankingUpdateAshmem={0}")
     public static Boolean[] getRankingUpdateAshmem() {
@@ -424,30 +421,11 @@
         mNotificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, "test channel",
                 NotificationManager.IMPORTANCE_DEFAULT);
 
-        SystemUiSystemPropertiesFlags.TEST_RESOLVER = new FlagResolver() {
-            @Override
-            public boolean isEnabled(Flag flag) {
-                if (flag.mSysPropKey.equals(RANKING_UPDATE_ASHMEM.mSysPropKey)) {
-                    return mRankingUpdateAshmem;
-                }
-                return new SystemUiSystemPropertiesFlags.DebugResolver().isEnabled(flag);
-            }
-
-            @Override
-            public int getIntValue(Flag flag) {
-                return 0;
-            }
-
-            @Override
-            public String getStringValue(Flag flag) {
-                return null;
-            }
-        };
-    }
-
-    @After
-    public void tearDown() {
-        SystemUiSystemPropertiesFlags.TEST_RESOLVER = null;
+        if (mRankingUpdateAshmem) {
+            mSetFlagsRule.enableFlags(Flags.FLAG_RANKING_UPDATE_ASHMEM);
+        } else {
+            mSetFlagsRule.disableFlags(Flags.FLAG_RANKING_UPDATE_ASHMEM);
+        }
     }
 
     /**
@@ -497,8 +475,7 @@
         parcel.setDataPosition(0);
         NotificationRankingUpdate nru1 = NotificationRankingUpdate.CREATOR.createFromParcel(parcel);
         // The rankingUpdate file descriptor is only non-null in the new path.
-        if (SystemUiSystemPropertiesFlags.getResolver().isEnabled(
-                SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) {
+        if (Flags.rankingUpdateAshmem()) {
             assertTrue(nru1.isFdNotNullAndClosed());
         }
         detailedAssertEquals(nru, nru1);
@@ -636,7 +613,7 @@
 
     @Test
     public void testRankingUpdate_writesSmartActionToParcel() {
-        if (!mRankingUpdateAshmem) {
+        if (!Flags.rankingUpdateAshmem()) {
             return;
         }
         ArrayList<Notification.Action> actions = new ArrayList<>();
@@ -674,7 +651,7 @@
 
     @Test
     public void testRankingUpdate_handlesEmptySmartActionList() {
-        if (!mRankingUpdateAshmem) {
+        if (!Flags.rankingUpdateAshmem()) {
             return;
         }
         ArrayList<Notification.Action> actions = new ArrayList<>();
@@ -697,7 +674,7 @@
 
     @Test
     public void testRankingUpdate_handlesNullSmartActionList() {
-        if (!mRankingUpdateAshmem) {
+        if (!Flags.rankingUpdateAshmem()) {
             return;
         }
         NotificationListenerService.Ranking ranking =
diff --git a/core/tests/coretests/src/android/view/OWNERS b/core/tests/coretests/src/android/view/OWNERS
index 2ca9994..23668a4 100644
--- a/core/tests/coretests/src/android/view/OWNERS
+++ b/core/tests/coretests/src/android/view/OWNERS
@@ -20,4 +20,7 @@
 per-file *ScrollCapture*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS
 
 # Stylus
-per-file stylus/* = file:/core/java/android/text/OWNERS
\ No newline at end of file
+per-file stylus/* = file:/core/java/android/text/OWNERS
+
+# View
+file:/core/java/android/view/OWNERS
\ No newline at end of file
diff --git a/graphics/java/android/framework_graphics.aconfig b/graphics/java/android/framework_graphics.aconfig
index e030dad..9a0a22a 100644
--- a/graphics/java/android/framework_graphics.aconfig
+++ b/graphics/java/android/framework_graphics.aconfig
@@ -2,7 +2,7 @@
 
 flag {
      name: "exact_compute_bounds"
-     namespace: "framework_graphics"
+     namespace: "core_graphics"
      description: "Add a function without unused exact param for computeBounds."
      bug: "304478551"
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index 7f02072..e111edc 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -39,7 +39,25 @@
 }
 
 filegroup {
-    name: "WMShellFlickerTestsPip-src",
+    name: "WMShellFlickerTestsPip1-src",
+    srcs: [
+        "src/com/android/wm/shell/flicker/pip/A*.kt",
+        "src/com/android/wm/shell/flicker/pip/B*.kt",
+        "src/com/android/wm/shell/flicker/pip/C*.kt",
+        "src/com/android/wm/shell/flicker/pip/D*.kt",
+        "src/com/android/wm/shell/flicker/pip/S*.kt",
+    ],
+}
+
+filegroup {
+    name: "WMShellFlickerTestsPip2-src",
+    srcs: [
+        "src/com/android/wm/shell/flicker/pip/E*.kt",
+    ],
+}
+
+filegroup {
+    name: "WMShellFlickerTestsPip3-src",
     srcs: ["src/com/android/wm/shell/flicker/pip/*.kt"],
 }
 
@@ -176,7 +194,9 @@
     ],
     exclude_srcs: [
         ":WMShellFlickerTestsBubbles-src",
-        ":WMShellFlickerTestsPip-src",
+        ":WMShellFlickerTestsPip1-src",
+        ":WMShellFlickerTestsPip2-src",
+        ":WMShellFlickerTestsPip3-src",
         ":WMShellFlickerTestsPipCommon-src",
         ":WMShellFlickerTestsPipApps-src",
         ":WMShellFlickerTestsSplitScreenGroup1-src",
@@ -207,12 +227,55 @@
     instrumentation_target_package: "com.android.wm.shell.flicker.pip",
     srcs: [
         ":WMShellFlickerTestsBase-src",
-        ":WMShellFlickerTestsPip-src",
+        ":WMShellFlickerTestsPip3-src",
         ":WMShellFlickerTestsPipCommon-src",
     ],
 }
 
 android_test {
+    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",
+        ":WMShellFlickerTestsPip2-src",
+        ":WMShellFlickerTestsPipCommon-src",
+    ],
+}
+
+android_test {
+    name: "WMShellFlickerTestsPip3",
+    defaults: ["WMShellFlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestPip.xml"],
+    package_name: "com.android.wm.shell.flicker.pip",
+    instrumentation_target_package: "com.android.wm.shell.flicker.pip",
+    srcs: [
+        ":WMShellFlickerTestsBase-src",
+        ":WMShellFlickerTestsPip3-src",
+        ":WMShellFlickerTestsPipCommon-src",
+    ],
+    exclude_srcs: [
+        ":WMShellFlickerTestsPip1-src",
+        ":WMShellFlickerTestsPip2-src",
+    ],
+}
+
+android_test {
     name: "WMShellFlickerTestsPipApps",
     defaults: ["WMShellFlickerTestsDefault"],
     additional_manifests: ["manifests/AndroidManifestPip.xml"],
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index 19c8435..94e3959 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -22,6 +22,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.EnterPipTransition
 import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -55,7 +56,7 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) :
-    EnterPipViaAppUiButtonTest(flicker) {
+    EnterPipTransition(flicker) {
     override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
 
     override val defaultEnterPip: FlickerBuilder.() -> Unit = {
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 7f22693..d056248 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -785,7 +785,7 @@
       has_locale = true;
     }
 
-    // if we don't have a result yet
+      // if we don't have a result yet
     if (!final_result ||
         // or this config is better before the locale than the existing result
         result->config.isBetterThanBeforeLocale(final_result->config, desired_config) ||
@@ -863,9 +863,12 @@
 
       // We can skip calling ResTable_config::match() if the caller does not care for the
       // configuration to match or if we're using the list of types that have already had their
-      // configuration matched.
+      // configuration matched. The exception to this is when the user has multiple locales set
+      // because the filtered list will then have values from multiple locales and we will need to
+      // call match() to make sure the current entry matches the config we are currently checking.
       const ResTable_config& this_config = type_entry->config;
-      if (!(use_filtered || ignore_configuration || this_config.match(desired_config))) {
+      if (!((use_filtered && (configurations_.size() == 1))
+          || ignore_configuration || this_config.match(desired_config))) {
         continue;
       }
 
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index b00fc69..14602ef 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -139,6 +139,7 @@
     mRenderNodes.clear();
     mRenderThread.cacheManager().unregisterCanvasContext(this);
     mRenderThread.renderState().removeContextCallback(this);
+    mHintSessionWrapper->destroy();
 }
 
 void CanvasContext::addRenderNode(RenderNode* node, bool placeFront) {
diff --git a/libs/hwui/renderthread/HintSessionWrapper.cpp b/libs/hwui/renderthread/HintSessionWrapper.cpp
index 1c3399a..2362331 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.cpp
+++ b/libs/hwui/renderthread/HintSessionWrapper.cpp
@@ -158,7 +158,6 @@
 void HintSessionWrapper::sendLoadIncreaseHint() {
     if (!init()) return;
     mBinding->sendHint(mHintSession, static_cast<int32_t>(SessionHint::CPU_LOAD_UP));
-    mLastFrameNotification = systemTime();
 }
 
 bool HintSessionWrapper::alive() {
diff --git a/libs/hwui/tests/unit/HintSessionWrapperTests.cpp b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
index a14ae1c..10a740a1 100644
--- a/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
+++ b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
@@ -259,6 +259,31 @@
 
 TEST_F(HintSessionWrapperTests, delayedDeletionDoesNotKillReusedSession) {
     EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(0);
+    EXPECT_CALL(*sMockBinding, fakeReportActualWorkDuration(sessionPtr, 5_ms)).Times(1);
+
+    mWrapper->init();
+    waitForWrapperReady();
+    // Init a second time just to grab the wrapper from the promise
+    mWrapper->init();
+    EXPECT_EQ(mWrapper->alive(), true);
+
+    // First schedule the deletion
+    scheduleDelayedDestroyManaged();
+
+    // Then, report an actual duration
+    mWrapper->reportActualWorkDuration(5_ms);
+
+    // Then, run the delayed deletion after sending the update
+    allowDelayedDestructionToStart();
+    waitForDelayedDestructionToFinish();
+
+    // Ensure it didn't close within the timeframe of the test
+    Mock::VerifyAndClearExpectations(sMockBinding.get());
+    EXPECT_EQ(mWrapper->alive(), true);
+}
+
+TEST_F(HintSessionWrapperTests, loadUpDoesNotResetDeletionTimer) {
+    EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
     EXPECT_CALL(*sMockBinding,
                 fakeSendHint(sessionPtr, static_cast<int32_t>(SessionHint::CPU_LOAD_UP)))
             .Times(1);
@@ -272,16 +297,46 @@
     // First schedule the deletion
     scheduleDelayedDestroyManaged();
 
-    // Then, send a hint to update the timestamp
+    // Then, send a load_up hint
     mWrapper->sendLoadIncreaseHint();
 
     // Then, run the delayed deletion after sending the update
     allowDelayedDestructionToStart();
     waitForDelayedDestructionToFinish();
 
-    // Ensure it didn't close within the timeframe of the test
+    // Ensure it closed within the timeframe of the test
     Mock::VerifyAndClearExpectations(sMockBinding.get());
+    EXPECT_EQ(mWrapper->alive(), false);
+}
+
+TEST_F(HintSessionWrapperTests, manualSessionDestroyPlaysNiceWithDelayedDestruct) {
+    EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
+
+    mWrapper->init();
+    waitForWrapperReady();
+    // Init a second time just to grab the wrapper from the promise
+    mWrapper->init();
     EXPECT_EQ(mWrapper->alive(), true);
+
+    // First schedule the deletion
+    scheduleDelayedDestroyManaged();
+
+    // Then, kill the session
+    mWrapper->destroy();
+
+    // Verify it died
+    Mock::VerifyAndClearExpectations(sMockBinding.get());
+    EXPECT_EQ(mWrapper->alive(), false);
+
+    EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(0);
+
+    // Then, run the delayed deletion after manually killing the session
+    allowDelayedDestructionToStart();
+    waitForDelayedDestructionToFinish();
+
+    // Ensure it didn't close again and is still dead
+    Mock::VerifyAndClearExpectations(sMockBinding.get());
+    EXPECT_EQ(mWrapper->alive(), false);
 }
 
 }  // namespace android::uirenderer::renderthread
\ No newline at end of file
diff --git a/media/Android.bp b/media/Android.bp
index f69dd3c..3493408 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -23,6 +23,10 @@
     name: "soundtrigger_middleware-aidl",
     unstable: true,
     local_include_dir: "aidl",
+    defaults: [
+        "latest_android_media_audio_common_types_import_interface",
+        "latest_android_media_soundtrigger_types_import_interface",
+    ],
     backend: {
         java: {
             sdk_version: "module_current",
@@ -32,8 +36,6 @@
         "aidl/android/media/soundtrigger_middleware/*.aidl",
     ],
     imports: [
-        "android.media.audio.common.types-V2",
-        "android.media.soundtrigger.types-V1",
         "media_permission-aidl",
     ],
 }
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 03f7c99..de73b77 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -32,22 +32,6 @@
       ]
     },
     {
-      "name": "SystemUIGoogleScreenshotTests",
-      "options": [
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "android.platform.test.annotations.Postsubmit"
-        }
-      ],
-      // The test doesn't run on AOSP Cuttlefish
-      "keywords": ["internal"]
-    },
-    {
       // TODO(b/251476085): Consider merging with SystemUIGoogleScreenshotTests (in U+)
       "name": "SystemUIGoogleBiometricsScreenshotTests",
       "options": [
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
index 0f55f35..eadcd7c 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
@@ -1,15 +1,17 @@
 package: "com.android.systemui.accessibility.accessibilitymenu"
 
-flag {
-    name: "a11y_menu_settings_back_button_fix_and_large_button_sizing"
-    namespace: "accessibility"
-    description: "Provides/restores back button functionality for the a11yMenu settings page. Also, fixes sizing problems with large shortcut buttons."
-    bug: "298467628"
-}
+# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
 
 flag {
     name: "a11y_menu_hide_before_taking_action"
     namespace: "accessibility"
     description: "Hides the AccessibilityMenuService UI before taking action instead of after."
     bug: "292020123"
-}
\ No newline at end of file
+}
+
+flag {
+    name: "a11y_menu_settings_back_button_fix_and_large_button_sizing"
+    namespace: "accessibility"
+    description: "Provides/restores back button functionality for the a11yMenu settings page. Also, fixes sizing problems with large shortcut buttons."
+    bug: "298467628"
+}
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig
index 8841967..bcf1535 100644
--- a/packages/SystemUI/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/aconfig/accessibility.aconfig
@@ -1,5 +1,7 @@
 package: "com.android.systemui"
 
+# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
+
 flag {
     name: "floating_menu_overlaps_nav_bars_flag"
     namespace: "accessibility"
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt
index 4ed78b3..33024f7 100644
--- a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt
+++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.communal.layout.ui.compose
 
+import android.util.SizeF
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
@@ -54,7 +55,14 @@
                         Row(
                             modifier = Modifier.height(layoutConfig.cardHeight(cardInfo.size)),
                         ) {
-                            cardInfo.card.Content(Modifier.fillMaxSize())
+                            cardInfo.card.Content(
+                                modifier = Modifier.fillMaxSize(),
+                                size =
+                                    SizeF(
+                                        layoutConfig.cardWidth.value,
+                                        layoutConfig.cardHeight(cardInfo.size).value,
+                                    ),
+                            )
                         }
                     }
                 }
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt
index ac8aa67..4b2a156 100644
--- a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt
+++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.communal.layout.ui.compose.config
 
+import android.util.SizeF
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 
@@ -26,8 +27,11 @@
      *
      * To host non-Compose views, see
      * https://developer.android.com/jetpack/compose/migrate/interoperability-apis/views-in-compose.
+     *
+     * @param size The size given to the card. Content of the card should fill all this space, given
+     *   that margins and paddings have been taken care of by the layout.
      */
-    @Composable abstract fun Content(modifier: Modifier)
+    @Composable abstract fun Content(modifier: Modifier, size: SizeF)
 
     /**
      * Sizes supported by the card.
diff --git a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt
index fdf65f5..c1974ca 100644
--- a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt
+++ b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt
@@ -1,5 +1,6 @@
 package com.android.systemui.communal.layout
 
+import android.util.SizeF
 import androidx.compose.material3.Card
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
@@ -91,7 +92,7 @@
             override val supportedSizes = listOf(size)
 
             @Composable
-            override fun Content(modifier: Modifier) {
+            override fun Content(modifier: Modifier, size: SizeF) {
                 Card(modifier = modifier, content = {})
             }
         }
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
index c6e429a..ddd1c67 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -72,6 +72,10 @@
         throwComposeUnavailableError()
     }
 
+    override fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View {
+        throwComposeUnavailableError()
+    }
+
     private fun throwComposeUnavailableError(): Nothing {
         error(
             "Compose is not available. Make sure to check isComposeAvailable() before calling any" +
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index 1722685..eeda6c6 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
 import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout
 import com.android.systemui.common.ui.compose.windowinsets.DisplayCutoutProvider
+import com.android.systemui.communal.ui.compose.CommunalContainer
 import com.android.systemui.communal.ui.compose.CommunalHub
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.people.ui.compose.PeopleScreen
@@ -104,6 +105,12 @@
         }
     }
 
+    override fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View {
+        return ComposeView(context).apply {
+            setContent { PlatformTheme { CommunalContainer(viewModel = viewModel) } }
+        }
+    }
+
     // TODO(b/298525212): remove once Compose exposes window inset bounds.
     private fun displayCutoutFromWindowInsets(
         scope: CoroutineScope,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
new file mode 100644
index 0000000..46d418a
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -0,0 +1,123 @@
+package com.android.systemui.communal.ui.compose
+
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.width
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.SceneTransitionLayout
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.transitions
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+
+object Scenes {
+    val Blank = SceneKey(name = "blank")
+    val Communal = SceneKey(name = "communal")
+}
+
+object Communal {
+    object Elements {
+        val Content = ElementKey("CommunalContent")
+    }
+}
+
+val sceneTransitions = transitions {
+    from(Scenes.Blank, to = Scenes.Communal) {
+        spec = tween(durationMillis = 500)
+
+        translate(Communal.Elements.Content, Edge.Right)
+        fade(Communal.Elements.Content)
+    }
+}
+
+/**
+ * View containing a [SceneTransitionLayout] that shows the communal UI and handles transitions.
+ *
+ * This is a temporary container to allow the communal UI to use [SceneTransitionLayout] for gesture
+ * handling and transitions before the full Flexiglass layout is ready.
+ */
+@Composable
+fun CommunalContainer(modifier: Modifier = Modifier, viewModel: CommunalViewModel) {
+    val (currentScene, setCurrentScene) = remember { mutableStateOf(Scenes.Blank) }
+
+    // Failsafe to hide the whole SceneTransitionLayout in case of bugginess.
+    var showSceneTransitionLayout by remember { mutableStateOf(true) }
+    if (!showSceneTransitionLayout) {
+        return
+    }
+
+    SceneTransitionLayout(
+        modifier = modifier.fillMaxSize(),
+        currentScene = currentScene,
+        onChangeScene = setCurrentScene,
+        transitions = sceneTransitions,
+    ) {
+        scene(Scenes.Blank, userActions = mapOf(Swipe.Left to Scenes.Communal)) {
+            BlankScene { showSceneTransitionLayout = false }
+        }
+
+        scene(
+            Scenes.Communal,
+            userActions = mapOf(Swipe.Right to Scenes.Blank),
+        ) {
+            CommunalScene(viewModel, modifier = modifier)
+        }
+    }
+}
+
+/**
+ * Blank scene that shows over keyguard/dream. This scene will eventually show nothing at all and is
+ * only used to allow for transitions to the communal scene.
+ */
+@Composable
+private fun BlankScene(
+    modifier: Modifier = Modifier,
+    hideSceneTransitionLayout: () -> Unit,
+) {
+    Box(modifier.fillMaxSize()) {
+        Column(
+            Modifier.fillMaxHeight()
+                .width(100.dp)
+                .align(Alignment.CenterEnd)
+                .background(Color(0x55e9f2eb)),
+            verticalArrangement = Arrangement.Center,
+            horizontalAlignment = Alignment.CenterHorizontally
+        ) {
+            Text("Default scene")
+
+            IconButton(onClick = hideSceneTransitionLayout) {
+                Icon(Icons.Filled.Close, contentDescription = "Close button")
+            }
+        }
+    }
+}
+
+/** Scene containing the glanceable hub UI. */
+@Composable
+private fun SceneScope.CommunalScene(
+    viewModel: CommunalViewModel,
+    modifier: Modifier = Modifier,
+) {
+    Box(modifier.element(Communal.Elements.Content)) { CommunalHub(viewModel = viewModel) }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 3d827fb..b8fb264 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -1,5 +1,8 @@
 package com.android.systemui.communal.ui.compose
 
+import android.appwidget.AppWidgetHostView
+import android.os.Bundle
+import android.util.SizeF
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
@@ -12,9 +15,12 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.res.integerResource
+import androidx.compose.ui.viewinterop.AndroidView
 import com.android.systemui.communal.layout.ui.compose.CommunalGridLayout
 import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard
 import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutConfig
+import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.ui.model.CommunalContentUiModel
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.res.R
 
@@ -24,6 +30,7 @@
     viewModel: CommunalViewModel,
 ) {
     val showTutorial by viewModel.showTutorialContent.collectAsState(initial = false)
+    val widgetContent by viewModel.widgetContent.collectAsState(initial = emptyList())
     Box(
         modifier = modifier.fillMaxSize().background(Color.White),
     ) {
@@ -36,7 +43,7 @@
                     gridHeight = dimensionResource(R.dimen.communal_grid_height),
                     gridColumnsPerCard = integerResource(R.integer.communal_grid_columns_per_card),
                 ),
-            communalCards = if (showTutorial) tutorialContent else emptyList(),
+            communalCards = if (showTutorial) tutorialContent else widgetContent.map(::contentCard),
         )
     }
 }
@@ -58,8 +65,37 @@
         override val supportedSizes = listOf(size)
 
         @Composable
-        override fun Content(modifier: Modifier) {
+        override fun Content(modifier: Modifier, size: SizeF) {
             Card(modifier = modifier, content = {})
         }
     }
 }
+
+private fun contentCard(model: CommunalContentUiModel): CommunalGridLayoutCard {
+    return object : CommunalGridLayoutCard() {
+        override val supportedSizes = listOf(convertToCardSize(model.size))
+        override val priority = model.priority
+
+        @Composable
+        override fun Content(modifier: Modifier, size: SizeF) {
+            AndroidView(
+                modifier = modifier,
+                factory = {
+                    model.view.apply {
+                        if (this is AppWidgetHostView) {
+                            updateAppWidgetSize(Bundle(), listOf(size))
+                        }
+                    }
+                },
+            )
+        }
+    }
+}
+
+private fun convertToCardSize(size: CommunalContentSize): CommunalGridLayoutCard.Size {
+    return when (size) {
+        CommunalContentSize.FULL -> CommunalGridLayoutCard.Size.FULL
+        CommunalContentSize.HALF -> CommunalGridLayoutCard.Size.HALF
+        CommunalContentSize.THIRD -> CommunalGridLayoutCard.Size.THIRD
+    }
+}
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index acee425..8957903 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -83,6 +83,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/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt
index db46ccf..80f70a0 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt
@@ -33,6 +33,10 @@
     val hasFingerprint: Boolean
         get() = fingerprintProperties != null
 
+    /** If SFPS authentication is available. */
+    val hasSfps: Boolean
+        get() = hasFingerprint && fingerprintProperties!!.isAnySidefpsType
+
     /** If fingerprint authentication is available (and [faceProperties] is non-null). */
     val hasFace: Boolean
         get() = faceProperties != null
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
index eb20669..c505bd5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
@@ -61,6 +61,8 @@
             InteractionJankMonitor.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS;
     public static final int CUJ_OPEN_SEARCH_RESULT =
             InteractionJankMonitor.CUJ_LAUNCHER_OPEN_SEARCH_RESULT;
+    public static final int CUJ_LAUNCHER_UNFOLD_ANIM =
+            InteractionJankMonitor.CUJ_LAUNCHER_UNFOLD_ANIM;
 
     @IntDef({
             CUJ_APP_LAUNCH_FROM_RECENTS,
@@ -77,6 +79,7 @@
             CUJ_CLOSE_ALL_APPS_SWIPE,
             CUJ_CLOSE_ALL_APPS_TO_HOME,
             CUJ_OPEN_SEARCH_RESULT,
+            CUJ_LAUNCHER_UNFOLD_ANIM,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
deleted file mode 100644
index 3f2da5e..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.biometrics
-
-import android.content.Context
-import android.graphics.drawable.Drawable
-import android.util.Log
-import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.res.R
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
-
-private const val TAG = "AuthBiometricFaceIconController"
-
-/** Face only icon animator for BiometricPrompt. */
-class AuthBiometricFaceIconController(
-        context: Context,
-        iconView: LottieAnimationView
-) : AuthIconController(context, iconView) {
-
-    // false = dark to light, true = light to dark
-    private var lastPulseLightToDark = false
-
-    private var state: BiometricState = BiometricState.STATE_IDLE
-
-    init {
-        val size = context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
-        iconView.layoutParams.width = size
-        iconView.layoutParams.height = size
-        showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light)
-    }
-
-    private fun startPulsing() {
-        lastPulseLightToDark = false
-        animateIcon(R.drawable.face_dialog_pulse_dark_to_light, true)
-    }
-
-    private fun pulseInNextDirection() {
-        val iconRes = if (lastPulseLightToDark) {
-            R.drawable.face_dialog_pulse_dark_to_light
-        } else {
-            R.drawable.face_dialog_pulse_light_to_dark
-        }
-        animateIcon(iconRes, true /* repeat */)
-        lastPulseLightToDark = !lastPulseLightToDark
-    }
-
-    override fun handleAnimationEnd(drawable: Drawable) {
-        if (state == BiometricState.STATE_AUTHENTICATING || state == BiometricState.STATE_HELP) {
-            pulseInNextDirection()
-        }
-    }
-
-    override fun updateIcon(oldState: BiometricState, newState: BiometricState) {
-        val lastStateIsErrorIcon = (oldState == BiometricState.STATE_ERROR || oldState == BiometricState.STATE_HELP)
-        if (newState == BiometricState.STATE_AUTHENTICATING_ANIMATING_IN) {
-            showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light)
-            iconView.contentDescription = context.getString(
-                    R.string.biometric_dialog_face_icon_description_authenticating
-            )
-        } else if (newState == BiometricState.STATE_AUTHENTICATING) {
-            startPulsing()
-            iconView.contentDescription = context.getString(
-                    R.string.biometric_dialog_face_icon_description_authenticating
-            )
-        } else if (oldState == BiometricState.STATE_PENDING_CONFIRMATION && newState == BiometricState.STATE_AUTHENTICATED) {
-            animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
-            iconView.contentDescription = context.getString(
-                    R.string.biometric_dialog_face_icon_description_confirmed
-            )
-        } else if (lastStateIsErrorIcon && newState == BiometricState.STATE_IDLE) {
-            animateIconOnce(R.drawable.face_dialog_error_to_idle)
-            iconView.contentDescription = context.getString(
-                    R.string.biometric_dialog_face_icon_description_idle
-            )
-        } else if (lastStateIsErrorIcon && newState == BiometricState.STATE_AUTHENTICATED) {
-            animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
-            iconView.contentDescription = context.getString(
-                    R.string.biometric_dialog_face_icon_description_authenticated
-            )
-        } else if (newState == BiometricState.STATE_ERROR && oldState != BiometricState.STATE_ERROR) {
-            animateIconOnce(R.drawable.face_dialog_dark_to_error)
-            iconView.contentDescription = context.getString(
-                    R.string.keyguard_face_failed
-            )
-        } else if (oldState == BiometricState.STATE_AUTHENTICATING && newState == BiometricState.STATE_AUTHENTICATED) {
-            animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
-            iconView.contentDescription = context.getString(
-                    R.string.biometric_dialog_face_icon_description_authenticated
-            )
-        } else if (newState == BiometricState.STATE_PENDING_CONFIRMATION) {
-            animateIconOnce(R.drawable.face_dialog_wink_from_dark)
-            iconView.contentDescription = context.getString(
-                    R.string.biometric_dialog_face_icon_description_authenticated
-            )
-        } else if (newState == BiometricState.STATE_IDLE) {
-            showStaticDrawable(R.drawable.face_dialog_idle_static)
-            iconView.contentDescription = context.getString(
-                    R.string.biometric_dialog_face_icon_description_idle
-            )
-        } else {
-            Log.w(TAG, "Unhandled state: $newState")
-        }
-        state = newState
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
deleted file mode 100644
index 09eabf2..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics
-
-import android.annotation.RawRes
-import android.content.Context
-import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.res.R
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATED
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_ERROR
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_HELP
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION
-
-/** Face/Fingerprint combined icon animator for BiometricPrompt. */
-open class AuthBiometricFingerprintAndFaceIconController(
-    context: Context,
-    iconView: LottieAnimationView,
-    iconViewOverlay: LottieAnimationView,
-) : AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay) {
-
-    override val actsAsConfirmButton: Boolean = true
-
-    override fun shouldAnimateIconViewForTransition(
-            oldState: BiometricState,
-            newState: BiometricState
-    ): Boolean = when (newState) {
-        STATE_PENDING_CONFIRMATION -> true
-        else -> super.shouldAnimateIconViewForTransition(oldState, newState)
-    }
-
-    @RawRes
-    override fun getAnimationForTransition(
-        oldState: BiometricState,
-        newState: BiometricState
-    ): Int? = when (newState) {
-        STATE_AUTHENTICATED -> {
-           if (oldState == STATE_PENDING_CONFIRMATION) {
-               R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie
-           } else {
-               super.getAnimationForTransition(oldState, newState)
-           }
-        }
-        STATE_PENDING_CONFIRMATION -> {
-            if (oldState == STATE_ERROR || oldState == STATE_HELP) {
-                R.raw.fingerprint_dialogue_error_to_unlock_lottie
-            } else {
-                R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie
-            }
-        }
-        else -> super.getAnimationForTransition(oldState, newState)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
deleted file mode 100644
index 0ad3848..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ /dev/null
@@ -1,342 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics
-
-import android.annotation.RawRes
-import android.content.Context
-import android.content.Context.FINGERPRINT_SERVICE
-import android.hardware.fingerprint.FingerprintManager
-import android.view.DisplayInfo
-import android.view.Surface
-import android.view.View
-import androidx.annotation.VisibleForTesting
-import com.airbnb.lottie.LottieAnimationView
-import com.android.settingslib.widget.LottieColorUtils
-import com.android.systemui.res.R
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATED
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATING
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATING_ANIMATING_IN
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_ERROR
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_HELP
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_IDLE
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION
-
-
-/** Fingerprint only icon animator for BiometricPrompt.  */
-open class AuthBiometricFingerprintIconController(
-        context: Context,
-        iconView: LottieAnimationView,
-        protected val iconViewOverlay: LottieAnimationView
-) : AuthIconController(context, iconView) {
-
-    private val isSideFps: Boolean
-    private val isReverseDefaultRotation =
-            context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
-
-    var iconLayoutParamSize: Pair<Int, Int> = Pair(1, 1)
-        set(value) {
-            if (field == value) {
-                return
-            }
-            iconViewOverlay.layoutParams.width = value.first
-            iconViewOverlay.layoutParams.height = value.second
-            iconView.layoutParams.width = value.first
-            iconView.layoutParams.height = value.second
-            field = value
-        }
-
-    init {
-        iconLayoutParamSize = Pair(context.resources.getDimensionPixelSize(
-                R.dimen.biometric_dialog_fingerprint_icon_width),
-                context.resources.getDimensionPixelSize(
-                        R.dimen.biometric_dialog_fingerprint_icon_height))
-        isSideFps =
-            (context.getSystemService(FINGERPRINT_SERVICE) as FingerprintManager?)?.let { fpm ->
-                fpm.sensorPropertiesInternal.any { it.isAnySidefpsType }
-            } ?: false
-        preloadAssets(context)
-        val displayInfo = DisplayInfo()
-        context.display?.getDisplayInfo(displayInfo)
-        if (isSideFps && getRotationFromDefault(displayInfo.rotation) == Surface.ROTATION_180) {
-            iconView.rotation = 180f
-        }
-    }
-
-    private fun updateIconSideFps(lastState: BiometricState, newState: BiometricState) {
-        val displayInfo = DisplayInfo()
-        context.display?.getDisplayInfo(displayInfo)
-        val rotation = getRotationFromDefault(displayInfo.rotation)
-        val iconViewOverlayAnimation =
-                getSideFpsOverlayAnimationForTransition(lastState, newState, rotation) ?: return
-
-        if (!(lastState == STATE_AUTHENTICATING_ANIMATING_IN && newState == STATE_AUTHENTICATING)) {
-            iconViewOverlay.setAnimation(iconViewOverlayAnimation)
-        }
-
-        val iconContentDescription = getIconContentDescription(newState)
-        if (iconContentDescription != null) {
-            iconView.contentDescription = iconContentDescription
-        }
-
-        iconView.frame = 0
-        iconViewOverlay.frame = 0
-        if (shouldAnimateSfpsIconViewForTransition(lastState, newState)) {
-            iconView.playAnimation()
-        }
-
-        if (shouldAnimateIconViewOverlayForTransition(lastState, newState)) {
-            iconViewOverlay.playAnimation()
-        }
-
-        LottieColorUtils.applyDynamicColors(context, iconView)
-        LottieColorUtils.applyDynamicColors(context, iconViewOverlay)
-    }
-
-    private fun updateIconNormal(lastState: BiometricState, newState: BiometricState) {
-        val icon = getAnimationForTransition(lastState, newState) ?: return
-
-        if (!(lastState == STATE_AUTHENTICATING_ANIMATING_IN && newState == STATE_AUTHENTICATING)) {
-            iconView.setAnimation(icon)
-        }
-
-        val iconContentDescription = getIconContentDescription(newState)
-        if (iconContentDescription != null) {
-            iconView.contentDescription = iconContentDescription
-        }
-
-        iconView.frame = 0
-        if (shouldAnimateIconViewForTransition(lastState, newState)) {
-            iconView.playAnimation()
-        }
-        LottieColorUtils.applyDynamicColors(context, iconView)
-    }
-
-    override fun updateIcon(lastState: BiometricState, newState: BiometricState) {
-        if (isSideFps) {
-            updateIconSideFps(lastState, newState)
-        } else {
-            iconViewOverlay.visibility = View.GONE
-            updateIconNormal(lastState, newState)
-        }
-    }
-
-    @VisibleForTesting
-    fun getIconContentDescription(newState: BiometricState): CharSequence? {
-        val id = when (newState) {
-            STATE_IDLE,
-            STATE_AUTHENTICATING_ANIMATING_IN,
-            STATE_AUTHENTICATING,
-            STATE_AUTHENTICATED ->
-                if (isSideFps) {
-                    R.string.security_settings_sfps_enroll_find_sensor_message
-                } else {
-                    R.string.fingerprint_dialog_touch_sensor
-                }
-            STATE_PENDING_CONFIRMATION ->
-                if (isSideFps) {
-                    R.string.security_settings_sfps_enroll_find_sensor_message
-                } else {
-                    R.string.fingerprint_dialog_authenticated_confirmation
-                }
-            STATE_ERROR,
-            STATE_HELP -> R.string.biometric_dialog_try_again
-            else -> null
-        }
-        return if (id != null) context.getString(id) else null
-    }
-
-    protected open fun shouldAnimateIconViewForTransition(
-            oldState: BiometricState,
-            newState: BiometricState
-    ) = when (newState) {
-        STATE_HELP,
-        STATE_ERROR -> true
-        STATE_AUTHENTICATING_ANIMATING_IN,
-        STATE_AUTHENTICATING -> oldState == STATE_ERROR || oldState == STATE_HELP
-        STATE_AUTHENTICATED -> true
-        else -> false
-    }
-
-    private fun shouldAnimateSfpsIconViewForTransition(
-            oldState: BiometricState,
-            newState: BiometricState
-    ) = when (newState) {
-        STATE_HELP,
-        STATE_ERROR -> true
-        STATE_AUTHENTICATING_ANIMATING_IN,
-        STATE_AUTHENTICATING ->
-            oldState == STATE_ERROR || oldState == STATE_HELP || oldState == STATE_IDLE
-        STATE_AUTHENTICATED -> true
-        else -> false
-    }
-
-    protected open fun shouldAnimateIconViewOverlayForTransition(
-            oldState: BiometricState,
-            newState: BiometricState
-    ) = when (newState) {
-        STATE_HELP,
-        STATE_ERROR -> true
-        STATE_AUTHENTICATING_ANIMATING_IN,
-        STATE_AUTHENTICATING -> oldState == STATE_ERROR || oldState == STATE_HELP
-        STATE_AUTHENTICATED -> true
-        else -> false
-    }
-
-    @RawRes
-    protected open fun getAnimationForTransition(
-            oldState: BiometricState,
-            newState: BiometricState
-    ): Int? {
-        val id = when (newState) {
-            STATE_HELP,
-            STATE_ERROR -> {
-                R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
-            }
-            STATE_AUTHENTICATING_ANIMATING_IN,
-            STATE_AUTHENTICATING -> {
-                if (oldState == STATE_ERROR || oldState == STATE_HELP) {
-                    R.raw.fingerprint_dialogue_error_to_fingerprint_lottie
-                } else {
-                    R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
-                }
-            }
-            STATE_AUTHENTICATED -> {
-                if (oldState == STATE_ERROR || oldState == STATE_HELP) {
-                    R.raw.fingerprint_dialogue_error_to_success_lottie
-                } else {
-                    R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
-                }
-            }
-            else -> return null
-        }
-        return if (id != null) return id else null
-    }
-
-    private fun getRotationFromDefault(rotation: Int): Int =
-            if (isReverseDefaultRotation) (rotation + 1) % 4 else rotation
-
-    @RawRes
-    private fun getSideFpsOverlayAnimationForTransition(
-            oldState: BiometricState,
-            newState: BiometricState,
-            rotation: Int
-    ): Int? = when (newState) {
-        STATE_HELP,
-        STATE_ERROR -> {
-            when (rotation) {
-                Surface.ROTATION_0 -> R.raw.biometricprompt_fingerprint_to_error_landscape
-                Surface.ROTATION_90 ->
-                    R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
-                Surface.ROTATION_180 ->
-                    R.raw.biometricprompt_fingerprint_to_error_landscape
-                Surface.ROTATION_270 ->
-                    R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
-                else -> R.raw.biometricprompt_fingerprint_to_error_landscape
-            }
-        }
-        STATE_AUTHENTICATING_ANIMATING_IN,
-        STATE_AUTHENTICATING -> {
-            if (oldState == STATE_ERROR || oldState == STATE_HELP) {
-                when (rotation) {
-                    Surface.ROTATION_0 ->
-                        R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
-                    Surface.ROTATION_90 ->
-                        R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft
-                    Surface.ROTATION_180 ->
-                        R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
-                    Surface.ROTATION_270 ->
-                        R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright
-                    else -> R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
-                }
-            } else {
-                when (rotation) {
-                    Surface.ROTATION_0 -> R.raw.biometricprompt_fingerprint_to_error_landscape
-                    Surface.ROTATION_90 ->
-                        R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
-                    Surface.ROTATION_180 ->
-                        R.raw.biometricprompt_fingerprint_to_error_landscape
-                    Surface.ROTATION_270 ->
-                        R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
-                    else -> R.raw.biometricprompt_fingerprint_to_error_landscape
-                }
-            }
-        }
-        STATE_AUTHENTICATED -> {
-            if (oldState == STATE_ERROR || oldState == STATE_HELP) {
-                when (rotation) {
-                    Surface.ROTATION_0 ->
-                        R.raw.biometricprompt_symbol_error_to_success_landscape
-                    Surface.ROTATION_90 ->
-                        R.raw.biometricprompt_symbol_error_to_success_portrait_topleft
-                    Surface.ROTATION_180 ->
-                        R.raw.biometricprompt_symbol_error_to_success_landscape
-                    Surface.ROTATION_270 ->
-                        R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright
-                    else -> R.raw.biometricprompt_symbol_error_to_success_landscape
-                }
-            } else {
-                when (rotation) {
-                    Surface.ROTATION_0 ->
-                        R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
-                    Surface.ROTATION_90 ->
-                        R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
-                    Surface.ROTATION_180 ->
-                        R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
-                    Surface.ROTATION_270 ->
-                        R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright
-                    else -> R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
-                }
-            }
-        }
-        else -> null
-    }
-
-    private fun preloadAssets(context: Context) {
-        if (isSideFps) {
-            cacheLottieAssetsInContext(
-                context,
-                R.raw.biometricprompt_fingerprint_to_error_landscape,
-                R.raw.biometricprompt_folded_base_bottomright,
-                R.raw.biometricprompt_folded_base_default,
-                R.raw.biometricprompt_folded_base_topleft,
-                R.raw.biometricprompt_landscape_base,
-                R.raw.biometricprompt_portrait_base_bottomright,
-                R.raw.biometricprompt_portrait_base_topleft,
-                R.raw.biometricprompt_symbol_error_to_fingerprint_landscape,
-                R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright,
-                R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft,
-                R.raw.biometricprompt_symbol_error_to_success_landscape,
-                R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright,
-                R.raw.biometricprompt_symbol_error_to_success_portrait_topleft,
-                R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright,
-                R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft,
-                R.raw.biometricprompt_symbol_fingerprint_to_success_landscape,
-                R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright,
-                R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
-            )
-        } else {
-            cacheLottieAssetsInContext(
-                context,
-                R.raw.fingerprint_dialogue_error_to_fingerprint_lottie,
-                R.raw.fingerprint_dialogue_error_to_success_lottie,
-                R.raw.fingerprint_dialogue_fingerprint_to_error_lottie,
-                R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
-            )
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
index 054bd08..8d1d905 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.systemui.biometrics
 
 import android.annotation.MainThread
@@ -25,7 +41,7 @@
             shadeExpansionCollectorJob =
                 scope.launch {
                     // wait for it to emit true once
-                    shadeInteractorLazy.get().isAnyExpanding.first { it }
+                    shadeInteractorLazy.get().isUserInteracting.first { it }
                     onShadeInteraction.run()
                 }
             shadeExpansionCollectorJob?.invokeOnCompletion { shadeExpansionCollectorJob = null }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt
deleted file mode 100644
index 958213a..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics
-
-import android.annotation.DrawableRes
-import android.content.Context
-import android.graphics.drawable.Animatable2
-import android.graphics.drawable.AnimatedVectorDrawable
-import android.graphics.drawable.Drawable
-import android.util.Log
-import com.airbnb.lottie.LottieAnimationView
-import com.airbnb.lottie.LottieCompositionFactory
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
-
-private const val TAG = "AuthIconController"
-
-/** Controller for animating the BiometricPrompt icon/affordance. */
-abstract class AuthIconController(
-    protected val context: Context,
-    protected val iconView: LottieAnimationView
-) : Animatable2.AnimationCallback() {
-
-    /** If this controller should ignore events and pause. */
-    var deactivated: Boolean = false
-
-    /** If the icon view should be treated as an alternate "confirm" button. */
-    open val actsAsConfirmButton: Boolean = false
-
-    final override fun onAnimationStart(drawable: Drawable) {
-        super.onAnimationStart(drawable)
-    }
-
-    final override fun onAnimationEnd(drawable: Drawable) {
-        super.onAnimationEnd(drawable)
-
-        if (!deactivated) {
-            handleAnimationEnd(drawable)
-        }
-    }
-
-    /** Set the icon to a static image. */
-    protected fun showStaticDrawable(@DrawableRes iconRes: Int) {
-        iconView.setImageDrawable(context.getDrawable(iconRes))
-    }
-
-    /** Animate a resource. */
-    protected fun animateIconOnce(@DrawableRes iconRes: Int) {
-        animateIcon(iconRes, false)
-    }
-
-    /** Animate a resource. */
-    protected fun animateIcon(@DrawableRes iconRes: Int, repeat: Boolean) {
-        if (!deactivated) {
-            val icon = context.getDrawable(iconRes) as AnimatedVectorDrawable
-            iconView.setImageDrawable(icon)
-            icon.forceAnimationOnUI()
-            if (repeat) {
-                icon.registerAnimationCallback(this)
-            }
-            icon.start()
-        }
-    }
-
-    /** Update the icon to reflect the [newState]. */
-    fun updateState(lastState: BiometricState, newState: BiometricState) {
-        if (deactivated) {
-            Log.w(TAG, "Ignoring updateState when deactivated: $newState")
-        } else {
-            updateIcon(lastState, newState)
-        }
-    }
-
-    /** Call during [updateState] if the controller is not [deactivated]. */
-    abstract fun updateIcon(lastState: BiometricState, newState: BiometricState)
-
-    /** Called during [onAnimationEnd] if the controller is not [deactivated]. */
-    open fun handleAnimationEnd(drawable: Drawable) {}
-
-    // TODO(b/251476085): Migrate this to an extension at the appropriate level?
-    /** Load the given [rawResources] immediately so they are cached for use in the [context]. */
-    protected fun cacheLottieAssetsInContext(context: Context, vararg rawResources: Int) {
-        for (res in rawResources) {
-            LottieCompositionFactory.fromRawRes(context, res)
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
index c4c52e8b..050b399 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
@@ -42,8 +42,11 @@
 /** Repository for the current state of the display */
 interface DisplayStateRepository {
     /**
-     * Whether or not the direction rotation is applied to get to an application's requested
-     * orientation is reversed.
+     * If true, the direction rotation is applied to get to an application's requested orientation
+     * is reversed. Normally, the model is that landscape is clockwise from portrait; thus on a
+     * portrait device an app requesting landscape will cause a clockwise rotation, and on a
+     * landscape device an app requesting portrait will cause a counter-clockwise rotation. Setting
+     * true here reverses that logic. See go/natural-orientation for context.
      */
     val isReverseDefaultRotation: Boolean
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
index a317a06..427361d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
@@ -57,6 +57,15 @@
     /** Display change event indicating a change to the given displayId has occurred. */
     val displayChanges: Flow<Int>
 
+    /**
+     * If true, the direction rotation is applied to get to an application's requested orientation
+     * is reversed. Normally, the model is that landscape is clockwise from portrait; thus on a
+     * portrait device an app requesting landscape will cause a clockwise rotation, and on a
+     * landscape device an app requesting portrait will cause a counter-clockwise rotation. Setting
+     * true here reverses that logic. See go/natural-orientation for context.
+     */
+    val isReverseDefaultRotation: Boolean
+
     /** Called on configuration changes, used to keep the display state in sync */
     fun onConfigurationChanged(newConfig: Configuration)
 }
@@ -112,6 +121,8 @@
     override val currentRotation: StateFlow<DisplayRotation> =
         displayStateRepository.currentRotation
 
+    override val isReverseDefaultRotation: Boolean = displayStateRepository.isReverseDefaultRotation
+
     override fun onConfigurationChanged(newConfig: Configuration) {
         screenSizeFoldProvider.onConfigurationChange(newConfig)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
index cef0be0..0d72b9c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
@@ -29,11 +29,10 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
-import com.android.systemui.res.R;
-import com.android.systemui.biometrics.AuthBiometricFingerprintIconController;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.AuthDialog;
 import com.android.systemui.biometrics.UdfpsDialogMeasureAdapter;
+import com.android.systemui.res.R;
 
 import kotlin.Pair;
 
@@ -85,13 +84,13 @@
     }
 
     @Deprecated
-    public void updateFingerprintAffordanceSize(
-            @NonNull AuthBiometricFingerprintIconController iconController) {
+    public Pair<Integer, Integer> getUpdatedFingerprintAffordanceSize() {
         if (mUdfpsAdapter != null) {
             final int sensorDiameter = mUdfpsAdapter.getSensorDiameter(
                     mScaleFactorProvider.provide());
-            iconController.setIconLayoutParamSize(new Pair(sensorDiameter, sensorDiameter));
+            return new Pair(sensorDiameter, sensorDiameter);
         }
+        return null;
     }
 
     @NonNull
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index c29efc0..ac48b6a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -38,11 +38,7 @@
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
 import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.res.R
-import com.android.systemui.biometrics.AuthBiometricFaceIconController
-import com.android.systemui.biometrics.AuthBiometricFingerprintAndFaceIconController
-import com.android.systemui.biometrics.AuthBiometricFingerprintIconController
-import com.android.systemui.biometrics.AuthIconController
+import com.airbnb.lottie.LottieCompositionFactory
 import com.android.systemui.biometrics.AuthPanelController
 import com.android.systemui.biometrics.shared.model.BiometricModalities
 import com.android.systemui.biometrics.shared.model.BiometricModality
@@ -56,6 +52,7 @@
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.VibratorHelper
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
@@ -101,10 +98,15 @@
             !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
         descriptionView.movementMethod = ScrollingMovementMethod()
 
-        val iconViewOverlay = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
+        val iconOverlayView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
         val iconView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon)
 
-        PromptFingerprintIconViewBinder.bind(iconView, viewModel.fingerprintIconViewModel)
+        PromptIconViewBinder.bind(
+            iconView,
+            iconOverlayView,
+            view.getUpdatedFingerprintAffordanceSize(),
+            viewModel.iconViewModel
+        )
 
         val indicatorMessageView = view.requireViewById<TextView>(R.id.indicator)
 
@@ -128,9 +130,21 @@
 
         // bind to prompt
         var boundSize = false
+
         view.repeatWhenAttached {
             // these do not change and need to be set before any size transitions
             val modalities = viewModel.modalities.first()
+            if (modalities.hasFingerprint) {
+                /**
+                 * Load the given [rawResources] immediately so they are cached for use in the
+                 * [context].
+                 */
+                val rawResources = viewModel.iconViewModel.getRawAssets(modalities.hasSfps)
+                for (res in rawResources) {
+                    LottieCompositionFactory.fromRawRes(view.context, res)
+                }
+            }
+
             titleView.text = viewModel.title.first()
             descriptionView.text = viewModel.description.first()
             subtitleView.text = viewModel.subtitle.first()
@@ -148,27 +162,8 @@
                 legacyCallback.onButtonTryAgain()
             }
 
-            // TODO(b/251476085): migrate legacy icon controllers and remove
-            var legacyState = viewModel.legacyState.value
-            val iconController =
-                modalities.asIconController(
-                    view.context,
-                    iconView,
-                    iconViewOverlay,
-                )
-            adapter.attach(this, iconController, modalities, legacyCallback)
-            if (iconController is AuthBiometricFingerprintIconController) {
-                view.updateFingerprintAffordanceSize(iconController)
-            }
-            if (iconController is HackyCoexIconController) {
-                iconController.faceMode = !viewModel.isConfirmationRequired.first()
-            }
+            adapter.attach(this, modalities, legacyCallback)
 
-            // the icon controller must be created before this happens for the legacy
-            // sizing code in BiometricPromptLayout to work correctly. Simplify this
-            // when those are also migrated. (otherwise the icon size may not be set to
-            // a pixel value before the view is measured and WRAP_CONTENT will be incorrectly
-            // used as part of the measure spec)
             if (!boundSize) {
                 boundSize = true
                 BiometricViewSizeBinder.bind(
@@ -212,14 +207,6 @@
                     ) {
                         legacyCallback.onStartDelayedFingerprintSensor()
                     }
-
-                    if (newMode.isStarted) {
-                        // do wonky switch from implicit to explicit flow
-                        (iconController as? HackyCoexIconController)?.faceMode = false
-                        viewModel.showAuthenticating(
-                            modalities.asDefaultHelpMessage(view.context),
-                        )
-                    }
                 }
             }
 
@@ -312,7 +299,7 @@
                     viewModel.isIconConfirmButton
                         .map { isPending ->
                             when {
-                                isPending && iconController.actsAsConfirmButton ->
+                                isPending && modalities.hasFaceAndFingerprint ->
                                     View.OnTouchListener { _: View, event: MotionEvent ->
                                         viewModel.onOverlayTouch(event)
                                     }
@@ -320,22 +307,11 @@
                             }
                         }
                         .collect { onTouch ->
-                            iconViewOverlay.setOnTouchListener(onTouch)
+                            iconOverlayView.setOnTouchListener(onTouch)
                             iconView.setOnTouchListener(onTouch)
                         }
                 }
 
-                // TODO(b/251476085): remove w/ legacy icon controllers
-                // set icon affordance using legacy states
-                // like the old code, this causes animations to repeat on config changes :(
-                // but keep behavior for now as no one has complained...
-                launch {
-                    viewModel.legacyState.collect { newState ->
-                        iconController.updateState(legacyState, newState)
-                        legacyState = newState
-                    }
-                }
-
                 // dismiss prompt when authenticated and confirmed
                 launch {
                     viewModel.isAuthenticated.collect { authState ->
@@ -350,7 +326,7 @@
 
                             // Allow icon to be used as confirmation button with a11y enabled
                             if (accessibilityManager.isTouchExplorationEnabled) {
-                                iconViewOverlay.setOnClickListener {
+                                iconOverlayView.setOnClickListener {
                                     viewModel.confirmAuthenticated()
                                 }
                                 iconView.setOnClickListener { viewModel.confirmAuthenticated() }
@@ -377,7 +353,6 @@
                 launch {
                     viewModel.message.collect { promptMessage ->
                         val isError = promptMessage is PromptMessage.Error
-
                         indicatorMessageView.text = promptMessage.message
                         indicatorMessageView.setTextColor(
                             if (isError) textColorError else textColorHint
@@ -472,9 +447,6 @@
     private var modalities: BiometricModalities = BiometricModalities()
     private var legacyCallback: Callback? = null
 
-    var legacyIconController: AuthIconController? = null
-        private set
-
     // hacky way to suppress lockout errors
     private val lockoutErrorStrings =
         listOf(
@@ -485,24 +457,20 @@
 
     fun attach(
         lifecycleOwner: LifecycleOwner,
-        iconController: AuthIconController,
         activeModalities: BiometricModalities,
         callback: Callback,
     ) {
         modalities = activeModalities
-        legacyIconController = iconController
         legacyCallback = callback
 
         lifecycleOwner.lifecycle.addObserver(
             object : DefaultLifecycleObserver {
                 override fun onCreate(owner: LifecycleOwner) {
                     lifecycleScope = owner.lifecycleScope
-                    iconController.deactivated = false
                 }
 
                 override fun onDestroy(owner: LifecycleOwner) {
                     lifecycleScope = null
-                    iconController.deactivated = true
                 }
             }
         )
@@ -626,61 +594,9 @@
         else -> ""
     }
 
-private fun BiometricModalities.asIconController(
-    context: Context,
-    iconView: LottieAnimationView,
-    iconViewOverlay: LottieAnimationView,
-): AuthIconController =
-    when {
-        hasFaceAndFingerprint -> HackyCoexIconController(context, iconView, iconViewOverlay)
-        hasFingerprint -> AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay)
-        hasFace -> AuthBiometricFaceIconController(context, iconView)
-        else -> throw IllegalStateException("unexpected view type :$this")
-    }
-
 private fun Boolean.asVisibleOrGone(): Int = if (this) View.VISIBLE else View.GONE
 
 private fun Boolean.asVisibleOrHidden(): Int = if (this) View.VISIBLE else View.INVISIBLE
 
 // TODO(b/251476085): proper type?
 typealias BiometricJankListener = Animator.AnimatorListener
-
-// TODO(b/251476085): delete - temporary until the legacy icon controllers are replaced
-private class HackyCoexIconController(
-    context: Context,
-    iconView: LottieAnimationView,
-    iconViewOverlay: LottieAnimationView,
-) : AuthBiometricFingerprintAndFaceIconController(context, iconView, iconViewOverlay) {
-
-    private var state: Spaghetti.BiometricState? = null
-    private val faceController = AuthBiometricFaceIconController(context, iconView)
-
-    var faceMode: Boolean = true
-        set(value) {
-            if (field != value) {
-                field = value
-
-                faceController.deactivated = !value
-                iconView.setImageIcon(null)
-                iconViewOverlay.setImageIcon(null)
-                state?.let { updateIcon(Spaghetti.BiometricState.STATE_IDLE, it) }
-            }
-        }
-
-    override fun updateIcon(
-        lastState: Spaghetti.BiometricState,
-        newState: Spaghetti.BiometricState,
-    ) {
-        if (deactivated) {
-            return
-        }
-
-        if (faceMode) {
-            faceController.updateIcon(lastState, newState)
-        } else {
-            super.updateIcon(lastState, newState)
-        }
-
-        state = newState
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptFingerprintIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptFingerprintIconViewBinder.kt
deleted file mode 100644
index d28f1dc..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptFingerprintIconViewBinder.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.biometrics.ui.binder
-
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
-import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.biometrics.ui.viewmodel.PromptFingerprintIconViewModel
-import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.launch
-
-/** Sub-binder for [BiometricPromptLayout.iconView]. */
-object PromptFingerprintIconViewBinder {
-
-    /** Binds [BiometricPromptLayout.iconView] to [PromptFingerprintIconViewModel]. */
-    @JvmStatic
-    fun bind(view: LottieAnimationView, viewModel: PromptFingerprintIconViewModel) {
-        view.repeatWhenAttached {
-            repeatOnLifecycle(Lifecycle.State.STARTED) {
-                viewModel.onConfigurationChanged(view.context.resources.configuration)
-                launch {
-                    viewModel.iconAsset.collect { iconAsset ->
-                        if (iconAsset != -1) {
-                            view.setAnimation(iconAsset)
-                            // TODO: must replace call below once non-sfps asset logic and
-                            // shouldAnimateIconView logic is migrated to this ViewModel.
-                            view.playAnimation()
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
new file mode 100644
index 0000000..475ef18
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.biometrics.ui.binder
+
+import android.graphics.drawable.Animatable2
+import android.graphics.drawable.AnimatedVectorDrawable
+import android.graphics.drawable.Drawable
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.airbnb.lottie.LottieAnimationView
+import com.android.settingslib.widget.LottieColorUtils
+import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel
+import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel.AuthType
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.util.kotlin.Utils.Companion.toQuad
+import com.android.systemui.util.kotlin.Utils.Companion.toQuint
+import com.android.systemui.util.kotlin.Utils.Companion.toTriple
+import com.android.systemui.util.kotlin.sample
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+
+/** Sub-binder for [BiometricPromptLayout.iconView]. */
+object PromptIconViewBinder {
+    /**
+     * Binds [BiometricPromptLayout.iconView] and [BiometricPromptLayout.biometric_icon_overlay] to
+     * [PromptIconViewModel].
+     */
+    @JvmStatic
+    fun bind(
+        iconView: LottieAnimationView,
+        iconOverlayView: LottieAnimationView,
+        iconViewLayoutParamSizeOverride: Pair<Int, Int>?,
+        viewModel: PromptIconViewModel
+    ) {
+        iconView.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                viewModel.onConfigurationChanged(iconView.context.resources.configuration)
+                if (iconViewLayoutParamSizeOverride != null) {
+                    iconView.layoutParams.width = iconViewLayoutParamSizeOverride.first
+                    iconView.layoutParams.height = iconViewLayoutParamSizeOverride.second
+
+                    iconOverlayView.layoutParams.width = iconViewLayoutParamSizeOverride.first
+                    iconOverlayView.layoutParams.height = iconViewLayoutParamSizeOverride.second
+                }
+
+                var faceIcon: AnimatedVectorDrawable? = null
+                val faceIconCallback =
+                    object : Animatable2.AnimationCallback() {
+                        override fun onAnimationStart(drawable: Drawable) {
+                            viewModel.onAnimationStart()
+                        }
+
+                        override fun onAnimationEnd(drawable: Drawable) {
+                            viewModel.onAnimationEnd()
+                        }
+                    }
+
+                launch {
+                    viewModel.activeAuthType.collect { activeAuthType ->
+                        if (iconViewLayoutParamSizeOverride == null) {
+                            val width: Int
+                            val height: Int
+                            when (activeAuthType) {
+                                AuthType.Fingerprint,
+                                AuthType.Coex -> {
+                                    width = viewModel.fingerprintIconWidth
+                                    height = viewModel.fingerprintIconHeight
+                                }
+                                AuthType.Face -> {
+                                    width = viewModel.faceIconWidth
+                                    height = viewModel.faceIconHeight
+                                }
+                            }
+
+                            iconView.layoutParams.width = width
+                            iconView.layoutParams.height = height
+
+                            iconOverlayView.layoutParams.width = width
+                            iconOverlayView.layoutParams.height = height
+                        }
+                    }
+                }
+
+                launch {
+                    viewModel.iconAsset
+                        .sample(
+                            combine(
+                                viewModel.activeAuthType,
+                                viewModel.shouldAnimateIconView,
+                                viewModel.shouldRepeatAnimation,
+                                viewModel.showingError,
+                                ::toQuad
+                            ),
+                            ::toQuint
+                        )
+                        .collect {
+                            (
+                                iconAsset,
+                                activeAuthType,
+                                shouldAnimateIconView,
+                                shouldRepeatAnimation,
+                                showingError) ->
+                            if (iconAsset != -1) {
+                                when (activeAuthType) {
+                                    AuthType.Fingerprint,
+                                    AuthType.Coex -> {
+                                        iconView.setAnimation(iconAsset)
+                                        iconView.frame = 0
+
+                                        if (shouldAnimateIconView) {
+                                            iconView.playAnimation()
+                                        }
+                                    }
+                                    AuthType.Face -> {
+                                        faceIcon?.apply {
+                                            unregisterAnimationCallback(faceIconCallback)
+                                            stop()
+                                        }
+                                        faceIcon =
+                                            iconView.context.getDrawable(iconAsset)
+                                                as AnimatedVectorDrawable
+                                        faceIcon?.apply {
+                                            iconView.setImageDrawable(this)
+                                            if (shouldAnimateIconView) {
+                                                forceAnimationOnUI()
+                                                if (shouldRepeatAnimation) {
+                                                    registerAnimationCallback(faceIconCallback)
+                                                }
+                                                start()
+                                            }
+                                        }
+                                    }
+                                }
+                                LottieColorUtils.applyDynamicColors(iconView.context, iconView)
+                                viewModel.setPreviousIconWasError(showingError)
+                            }
+                        }
+                }
+
+                launch {
+                    viewModel.iconOverlayAsset
+                        .sample(
+                            combine(
+                                viewModel.shouldAnimateIconOverlay,
+                                viewModel.showingError,
+                                ::Pair
+                            ),
+                            ::toTriple
+                        )
+                        .collect { (iconOverlayAsset, shouldAnimateIconOverlay, showingError) ->
+                            if (iconOverlayAsset != -1) {
+                                iconOverlayView.setAnimation(iconOverlayAsset)
+                                iconOverlayView.frame = 0
+                                LottieColorUtils.applyDynamicColors(
+                                    iconOverlayView.context,
+                                    iconOverlayView
+                                )
+
+                                if (shouldAnimateIconOverlay) {
+                                    iconOverlayView.playAnimation()
+                                }
+                                viewModel.setPreviousIconOverlayWasError(showingError)
+                            }
+                        }
+                }
+
+                launch {
+                    viewModel.shouldFlipIconView.collect { shouldFlipIconView ->
+                        if (shouldFlipIconView) {
+                            iconView.rotation = 180f
+                        }
+                    }
+                }
+
+                launch {
+                    viewModel.contentDescriptionId.collect { id ->
+                        if (id != -1) {
+                            iconView.contentDescription = iconView.context.getString(id)
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModel.kt
deleted file mode 100644
index dfd3a9b..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModel.kt
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.biometrics.ui.viewmodel
-
-import android.annotation.RawRes
-import android.content.res.Configuration
-import com.android.systemui.res.R
-import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
-import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
-import com.android.systemui.biometrics.shared.model.DisplayRotation
-import com.android.systemui.biometrics.shared.model.FingerprintSensorType
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-
-/** Models UI of [BiometricPromptLayout.iconView] */
-class PromptFingerprintIconViewModel
-@Inject
-constructor(
-    private val displayStateInteractor: DisplayStateInteractor,
-    promptSelectorInteractor: PromptSelectorInteractor,
-) {
-    /** Current BiometricPromptLayout.iconView asset. */
-    val iconAsset: Flow<Int> =
-        combine(
-            displayStateInteractor.currentRotation,
-            displayStateInteractor.isFolded,
-            displayStateInteractor.isInRearDisplayMode,
-            promptSelectorInteractor.sensorType,
-        ) {
-            rotation: DisplayRotation,
-            isFolded: Boolean,
-            isInRearDisplayMode: Boolean,
-            sensorType: FingerprintSensorType ->
-            when (sensorType) {
-                FingerprintSensorType.POWER_BUTTON ->
-                    getSideFpsAnimationAsset(rotation, isFolded, isInRearDisplayMode)
-                // Replace below when non-SFPS iconAsset logic is migrated to this ViewModel
-                else -> -1
-            }
-        }
-
-    @RawRes
-    private fun getSideFpsAnimationAsset(
-        rotation: DisplayRotation,
-        isDeviceFolded: Boolean,
-        isInRearDisplayMode: Boolean,
-    ): Int =
-        when (rotation) {
-            DisplayRotation.ROTATION_90 ->
-                if (isInRearDisplayMode) {
-                    R.raw.biometricprompt_rear_portrait_reverse_base
-                } else if (isDeviceFolded) {
-                    R.raw.biometricprompt_folded_base_topleft
-                } else {
-                    R.raw.biometricprompt_portrait_base_topleft
-                }
-            DisplayRotation.ROTATION_270 ->
-                if (isInRearDisplayMode) {
-                    R.raw.biometricprompt_rear_portrait_base
-                } else if (isDeviceFolded) {
-                    R.raw.biometricprompt_folded_base_bottomright
-                } else {
-                    R.raw.biometricprompt_portrait_base_bottomright
-                }
-            else ->
-                if (isInRearDisplayMode) {
-                    R.raw.biometricprompt_rear_landscape_base
-                } else if (isDeviceFolded) {
-                    R.raw.biometricprompt_folded_base_default
-                } else {
-                    R.raw.biometricprompt_landscape_base
-                }
-        }
-
-    /** Called on configuration changes */
-    fun onConfigurationChanged(newConfig: Configuration) {
-        displayStateInteractor.onConfigurationChanged(newConfig)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
new file mode 100644
index 0000000..11a5d8b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
@@ -0,0 +1,721 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.biometrics.ui.viewmodel
+
+import android.annotation.DrawableRes
+import android.annotation.RawRes
+import android.content.res.Configuration
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
+import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
+import com.android.systemui.biometrics.shared.model.DisplayRotation
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.res.R
+import com.android.systemui.util.kotlin.combine
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+/**
+ * Models UI of [BiometricPromptLayout.iconView] and [BiometricPromptLayout.biometric_icon_overlay]
+ */
+class PromptIconViewModel
+constructor(
+    promptViewModel: PromptViewModel,
+    private val displayStateInteractor: DisplayStateInteractor,
+    promptSelectorInteractor: PromptSelectorInteractor
+) {
+
+    /** Auth types for the UI to display. */
+    enum class AuthType {
+        Fingerprint,
+        Face,
+        Coex
+    }
+
+    /**
+     * Indicates what auth type the UI currently displays.
+     * Fingerprint-only auth -> Fingerprint
+     * Face-only auth -> Face
+     * Co-ex auth, implicit flow -> Face
+     * Co-ex auth, explicit flow -> Coex
+     */
+    val activeAuthType: Flow<AuthType> =
+        combine(
+            promptViewModel.modalities.distinctUntilChanged(),
+            promptViewModel.faceMode.distinctUntilChanged()
+        ) { modalities, faceMode ->
+            if (modalities.hasFaceAndFingerprint && !faceMode) {
+                AuthType.Coex
+            } else if (modalities.hasFaceOnly || faceMode) {
+                AuthType.Face
+            } else if (modalities.hasFingerprintOnly) {
+                AuthType.Fingerprint
+            } else {
+                throw IllegalStateException("unexpected modality: $modalities")
+            }
+        }
+
+    /** Whether an error message is currently being shown. */
+    val showingError = promptViewModel.showingError
+
+    /** Whether the previous icon shown displayed an error. */
+    private val _previousIconWasError: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+    /** Whether the previous icon overlay shown displayed an error. */
+    private val _previousIconOverlayWasError: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+    fun setPreviousIconWasError(previousIconWasError: Boolean) {
+        _previousIconWasError.value = previousIconWasError
+    }
+
+    fun setPreviousIconOverlayWasError(previousIconOverlayWasError: Boolean) {
+        _previousIconOverlayWasError.value = previousIconOverlayWasError
+    }
+
+    /** Called when iconView begins animating. */
+    fun onAnimationStart() {
+        _animationEnded.value = false
+    }
+
+    /** Called when iconView ends animating. */
+    fun onAnimationEnd() {
+        _animationEnded.value = true
+    }
+
+    private val _animationEnded: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+    /**
+     * Whether a face iconView should pulse (i.e. while isAuthenticating and previous animation
+     * ended).
+     */
+    val shouldPulseAnimation: Flow<Boolean> =
+        combine(_animationEnded, promptViewModel.isAuthenticating) {
+                animationEnded,
+                isAuthenticating ->
+                animationEnded && isAuthenticating
+            }
+            .distinctUntilChanged()
+
+    private val _lastPulseLightToDark: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+    /** Tracks whether a face iconView last pulsed light to dark (vs. dark to light) */
+    val lastPulseLightToDark: Flow<Boolean> = _lastPulseLightToDark.asStateFlow()
+
+    /** Layout params for fingerprint iconView */
+    val fingerprintIconWidth: Int = promptViewModel.fingerprintIconWidth
+    val fingerprintIconHeight: Int = promptViewModel.fingerprintIconHeight
+
+    /** Layout params for face iconView */
+    val faceIconWidth: Int = promptViewModel.faceIconWidth
+    val faceIconHeight: Int = promptViewModel.faceIconHeight
+
+    /** Current BiometricPromptLayout.iconView asset. */
+    val iconAsset: Flow<Int> =
+        activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+            when (activeAuthType) {
+                AuthType.Fingerprint ->
+                    combine(
+                        displayStateInteractor.currentRotation,
+                        displayStateInteractor.isFolded,
+                        displayStateInteractor.isInRearDisplayMode,
+                        promptSelectorInteractor.sensorType,
+                        promptViewModel.isAuthenticated,
+                        promptViewModel.isAuthenticating,
+                        promptViewModel.showingError
+                    ) {
+                        rotation: DisplayRotation,
+                        isFolded: Boolean,
+                        isInRearDisplayMode: Boolean,
+                        sensorType: FingerprintSensorType,
+                        authState: PromptAuthState,
+                        isAuthenticating: Boolean,
+                        showingError: Boolean ->
+                        when (sensorType) {
+                            FingerprintSensorType.POWER_BUTTON ->
+                                getSfpsIconViewAsset(rotation, isFolded, isInRearDisplayMode)
+                            else ->
+                                getFingerprintIconViewAsset(
+                                    authState.isAuthenticated,
+                                    isAuthenticating,
+                                    showingError
+                                )
+                        }
+                    }
+                AuthType.Face ->
+                    shouldPulseAnimation.flatMapLatest { shouldPulseAnimation: Boolean ->
+                        if (shouldPulseAnimation) {
+                            val iconAsset =
+                                if (_lastPulseLightToDark.value) {
+                                    R.drawable.face_dialog_pulse_dark_to_light
+                                } else {
+                                    R.drawable.face_dialog_pulse_light_to_dark
+                                }
+                            _lastPulseLightToDark.value = !_lastPulseLightToDark.value
+                            flowOf(iconAsset)
+                        } else {
+                            combine(
+                                promptViewModel.isAuthenticated.distinctUntilChanged(),
+                                promptViewModel.isAuthenticating.distinctUntilChanged(),
+                                promptViewModel.isPendingConfirmation.distinctUntilChanged(),
+                                promptViewModel.showingError.distinctUntilChanged()
+                            ) {
+                                authState: PromptAuthState,
+                                isAuthenticating: Boolean,
+                                isPendingConfirmation: Boolean,
+                                showingError: Boolean ->
+                                getFaceIconViewAsset(
+                                    authState,
+                                    isAuthenticating,
+                                    isPendingConfirmation,
+                                    showingError
+                                )
+                            }
+                        }
+                    }
+                AuthType.Coex ->
+                    combine(
+                        displayStateInteractor.currentRotation,
+                        displayStateInteractor.isFolded,
+                        displayStateInteractor.isInRearDisplayMode,
+                        promptSelectorInteractor.sensorType,
+                        promptViewModel.isAuthenticated,
+                        promptViewModel.isAuthenticating,
+                        promptViewModel.isPendingConfirmation,
+                        promptViewModel.showingError,
+                    ) {
+                        rotation: DisplayRotation,
+                        isFolded: Boolean,
+                        isInRearDisplayMode: Boolean,
+                        sensorType: FingerprintSensorType,
+                        authState: PromptAuthState,
+                        isAuthenticating: Boolean,
+                        isPendingConfirmation: Boolean,
+                        showingError: Boolean ->
+                        when (sensorType) {
+                            FingerprintSensorType.POWER_BUTTON ->
+                                getSfpsIconViewAsset(rotation, isFolded, isInRearDisplayMode)
+                            else ->
+                                getCoexIconViewAsset(
+                                    authState,
+                                    isAuthenticating,
+                                    isPendingConfirmation,
+                                    showingError
+                                )
+                        }
+                    }
+            }
+        }
+
+    private fun getFingerprintIconViewAsset(
+        isAuthenticated: Boolean,
+        isAuthenticating: Boolean,
+        showingError: Boolean
+    ): Int =
+        if (isAuthenticated) {
+            if (_previousIconWasError.value) {
+                R.raw.fingerprint_dialogue_error_to_success_lottie
+            } else {
+                R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
+            }
+        } else if (isAuthenticating) {
+            if (_previousIconWasError.value) {
+                R.raw.fingerprint_dialogue_error_to_fingerprint_lottie
+            } else {
+                R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
+            }
+        } else if (showingError) {
+            R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
+        } else {
+            -1
+        }
+
+    @RawRes
+    private fun getSfpsIconViewAsset(
+        rotation: DisplayRotation,
+        isDeviceFolded: Boolean,
+        isInRearDisplayMode: Boolean,
+    ): Int =
+        when (rotation) {
+            DisplayRotation.ROTATION_90 ->
+                if (isInRearDisplayMode) {
+                    R.raw.biometricprompt_rear_portrait_reverse_base
+                } else if (isDeviceFolded) {
+                    R.raw.biometricprompt_folded_base_topleft
+                } else {
+                    R.raw.biometricprompt_portrait_base_topleft
+                }
+            DisplayRotation.ROTATION_270 ->
+                if (isInRearDisplayMode) {
+                    R.raw.biometricprompt_rear_portrait_base
+                } else if (isDeviceFolded) {
+                    R.raw.biometricprompt_folded_base_bottomright
+                } else {
+                    R.raw.biometricprompt_portrait_base_bottomright
+                }
+            else ->
+                if (isInRearDisplayMode) {
+                    R.raw.biometricprompt_rear_landscape_base
+                } else if (isDeviceFolded) {
+                    R.raw.biometricprompt_folded_base_default
+                } else {
+                    R.raw.biometricprompt_landscape_base
+                }
+        }
+
+    @DrawableRes
+    private fun getFaceIconViewAsset(
+        authState: PromptAuthState,
+        isAuthenticating: Boolean,
+        isPendingConfirmation: Boolean,
+        showingError: Boolean
+    ): Int =
+        if (authState.isAuthenticated && isPendingConfirmation) {
+            R.drawable.face_dialog_wink_from_dark
+        } else if (authState.isAuthenticated) {
+            R.drawable.face_dialog_dark_to_checkmark
+        } else if (isAuthenticating) {
+            _lastPulseLightToDark.value = false
+            R.drawable.face_dialog_pulse_dark_to_light
+        } else if (showingError) {
+            R.drawable.face_dialog_dark_to_error
+        } else if (_previousIconWasError.value) {
+            R.drawable.face_dialog_error_to_idle
+        } else {
+            R.drawable.face_dialog_idle_static
+        }
+
+    @RawRes
+    private fun getCoexIconViewAsset(
+        authState: PromptAuthState,
+        isAuthenticating: Boolean,
+        isPendingConfirmation: Boolean,
+        showingError: Boolean
+    ): Int =
+        if (authState.isAuthenticatedAndExplicitlyConfirmed) {
+            R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie
+        } else if (isPendingConfirmation) {
+            if (_previousIconWasError.value) {
+                R.raw.fingerprint_dialogue_error_to_unlock_lottie
+            } else {
+                R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie
+            }
+        } else if (authState.isAuthenticated) {
+            if (_previousIconWasError.value) {
+                R.raw.fingerprint_dialogue_error_to_success_lottie
+            } else {
+                R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
+            }
+        } else if (isAuthenticating) {
+            if (_previousIconWasError.value) {
+                R.raw.fingerprint_dialogue_error_to_fingerprint_lottie
+            } else {
+                R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
+            }
+        } else if (showingError) {
+            R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
+        } else {
+            -1
+        }
+
+    /** Current BiometricPromptLayout.biometric_icon_overlay asset. */
+    var iconOverlayAsset: Flow<Int> =
+        activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+            when (activeAuthType) {
+                AuthType.Fingerprint,
+                AuthType.Coex ->
+                    combine(
+                        displayStateInteractor.currentRotation,
+                        promptSelectorInteractor.sensorType,
+                        promptViewModel.isAuthenticated,
+                        promptViewModel.isAuthenticating,
+                        promptViewModel.showingError
+                    ) {
+                        rotation: DisplayRotation,
+                        sensorType: FingerprintSensorType,
+                        authState: PromptAuthState,
+                        isAuthenticating: Boolean,
+                        showingError: Boolean ->
+                        when (sensorType) {
+                            FingerprintSensorType.POWER_BUTTON ->
+                                getSfpsIconOverlayAsset(
+                                    rotation,
+                                    authState.isAuthenticated,
+                                    isAuthenticating,
+                                    showingError
+                                )
+                            else -> -1
+                        }
+                    }
+                AuthType.Face -> flowOf(-1)
+            }
+        }
+
+    @RawRes
+    private fun getSfpsIconOverlayAsset(
+        rotation: DisplayRotation,
+        isAuthenticated: Boolean,
+        isAuthenticating: Boolean,
+        showingError: Boolean
+    ): Int =
+        if (isAuthenticated) {
+            if (_previousIconOverlayWasError.value) {
+                when (rotation) {
+                    DisplayRotation.ROTATION_0 ->
+                        R.raw.biometricprompt_symbol_error_to_success_landscape
+                    DisplayRotation.ROTATION_90 ->
+                        R.raw.biometricprompt_symbol_error_to_success_portrait_topleft
+                    DisplayRotation.ROTATION_180 ->
+                        R.raw.biometricprompt_symbol_error_to_success_landscape
+                    DisplayRotation.ROTATION_270 ->
+                        R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright
+                }
+            } else {
+                when (rotation) {
+                    DisplayRotation.ROTATION_0 ->
+                        R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
+                    DisplayRotation.ROTATION_90 ->
+                        R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
+                    DisplayRotation.ROTATION_180 ->
+                        R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
+                    DisplayRotation.ROTATION_270 ->
+                        R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright
+                }
+            }
+        } else if (isAuthenticating) {
+            if (_previousIconOverlayWasError.value) {
+                when (rotation) {
+                    DisplayRotation.ROTATION_0 ->
+                        R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
+                    DisplayRotation.ROTATION_90 ->
+                        R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft
+                    DisplayRotation.ROTATION_180 ->
+                        R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
+                    DisplayRotation.ROTATION_270 ->
+                        R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright
+                }
+            } else {
+                when (rotation) {
+                    DisplayRotation.ROTATION_0 ->
+                        R.raw.biometricprompt_fingerprint_to_error_landscape
+                    DisplayRotation.ROTATION_90 ->
+                        R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
+                    DisplayRotation.ROTATION_180 ->
+                        R.raw.biometricprompt_fingerprint_to_error_landscape
+                    DisplayRotation.ROTATION_270 ->
+                        R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
+                }
+            }
+        } else if (showingError) {
+            when (rotation) {
+                DisplayRotation.ROTATION_0 -> R.raw.biometricprompt_fingerprint_to_error_landscape
+                DisplayRotation.ROTATION_90 ->
+                    R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
+                DisplayRotation.ROTATION_180 -> R.raw.biometricprompt_fingerprint_to_error_landscape
+                DisplayRotation.ROTATION_270 ->
+                    R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
+            }
+        } else {
+            -1
+        }
+
+    /** Content description for iconView */
+    val contentDescriptionId: Flow<Int> =
+        activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+            when (activeAuthType) {
+                AuthType.Fingerprint,
+                AuthType.Coex ->
+                    combine(
+                        promptSelectorInteractor.sensorType,
+                        promptViewModel.isAuthenticated,
+                        promptViewModel.isAuthenticating,
+                        promptViewModel.isPendingConfirmation,
+                        promptViewModel.showingError
+                    ) {
+                        sensorType: FingerprintSensorType,
+                        authState: PromptAuthState,
+                        isAuthenticating: Boolean,
+                        isPendingConfirmation: Boolean,
+                        showingError: Boolean ->
+                        getFingerprintIconContentDescriptionId(
+                            sensorType,
+                            authState.isAuthenticated,
+                            isAuthenticating,
+                            isPendingConfirmation,
+                            showingError
+                        )
+                    }
+                AuthType.Face ->
+                    combine(
+                        promptViewModel.isAuthenticated,
+                        promptViewModel.isAuthenticating,
+                        promptViewModel.showingError,
+                    ) { authState: PromptAuthState, isAuthenticating: Boolean, showingError: Boolean
+                        ->
+                        getFaceIconContentDescriptionId(authState, isAuthenticating, showingError)
+                    }
+            }
+        }
+
+    private fun getFingerprintIconContentDescriptionId(
+        sensorType: FingerprintSensorType,
+        isAuthenticated: Boolean,
+        isAuthenticating: Boolean,
+        isPendingConfirmation: Boolean,
+        showingError: Boolean
+    ): Int =
+        if (isPendingConfirmation) {
+            when (sensorType) {
+                FingerprintSensorType.POWER_BUTTON ->
+                    R.string.security_settings_sfps_enroll_find_sensor_message
+                else -> R.string.fingerprint_dialog_authenticated_confirmation
+            }
+        } else if (isAuthenticating || isAuthenticated) {
+            when (sensorType) {
+                FingerprintSensorType.POWER_BUTTON ->
+                    R.string.security_settings_sfps_enroll_find_sensor_message
+                else -> R.string.fingerprint_dialog_touch_sensor
+            }
+        } else if (showingError) {
+            R.string.biometric_dialog_try_again
+        } else {
+            -1
+        }
+
+    private fun getFaceIconContentDescriptionId(
+        authState: PromptAuthState,
+        isAuthenticating: Boolean,
+        showingError: Boolean
+    ): Int =
+        if (authState.isAuthenticatedAndExplicitlyConfirmed) {
+            R.string.biometric_dialog_face_icon_description_confirmed
+        } else if (authState.isAuthenticated) {
+            R.string.biometric_dialog_face_icon_description_authenticated
+        } else if (isAuthenticating) {
+            R.string.biometric_dialog_face_icon_description_authenticating
+        } else if (showingError) {
+            R.string.keyguard_face_failed
+        } else {
+            R.string.biometric_dialog_face_icon_description_idle
+        }
+
+    /** Whether the current BiometricPromptLayout.iconView asset animation should be playing. */
+    val shouldAnimateIconView: Flow<Boolean> =
+        activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+            when (activeAuthType) {
+                AuthType.Fingerprint ->
+                    combine(
+                        promptSelectorInteractor.sensorType,
+                        promptViewModel.isAuthenticated,
+                        promptViewModel.isAuthenticating,
+                        promptViewModel.showingError
+                    ) {
+                        sensorType: FingerprintSensorType,
+                        authState: PromptAuthState,
+                        isAuthenticating: Boolean,
+                        showingError: Boolean ->
+                        when (sensorType) {
+                            FingerprintSensorType.POWER_BUTTON ->
+                                shouldAnimateSfpsIconView(
+                                    authState.isAuthenticated,
+                                    isAuthenticating,
+                                    showingError
+                                )
+                            else ->
+                                shouldAnimateFingerprintIconView(
+                                    authState.isAuthenticated,
+                                    isAuthenticating,
+                                    showingError
+                                )
+                        }
+                    }
+                AuthType.Face ->
+                    combine(
+                        promptViewModel.isAuthenticated,
+                        promptViewModel.isAuthenticating,
+                        promptViewModel.showingError
+                    ) { authState: PromptAuthState, isAuthenticating: Boolean, showingError: Boolean
+                        ->
+                        isAuthenticating ||
+                            authState.isAuthenticated ||
+                            showingError ||
+                            _previousIconWasError.value
+                    }
+                AuthType.Coex ->
+                    combine(
+                        promptSelectorInteractor.sensorType,
+                        promptViewModel.isAuthenticated,
+                        promptViewModel.isAuthenticating,
+                        promptViewModel.isPendingConfirmation,
+                        promptViewModel.showingError,
+                    ) {
+                        sensorType: FingerprintSensorType,
+                        authState: PromptAuthState,
+                        isAuthenticating: Boolean,
+                        isPendingConfirmation: Boolean,
+                        showingError: Boolean ->
+                        when (sensorType) {
+                            FingerprintSensorType.POWER_BUTTON ->
+                                shouldAnimateSfpsIconView(
+                                    authState.isAuthenticated,
+                                    isAuthenticating,
+                                    showingError
+                                )
+                            else ->
+                                shouldAnimateCoexIconView(
+                                    authState.isAuthenticated,
+                                    isAuthenticating,
+                                    isPendingConfirmation,
+                                    showingError
+                                )
+                        }
+                    }
+            }
+        }
+
+    private fun shouldAnimateFingerprintIconView(
+        isAuthenticated: Boolean,
+        isAuthenticating: Boolean,
+        showingError: Boolean
+    ) = (isAuthenticating && _previousIconWasError.value) || isAuthenticated || showingError
+
+    private fun shouldAnimateSfpsIconView(
+        isAuthenticated: Boolean,
+        isAuthenticating: Boolean,
+        showingError: Boolean
+    ) = isAuthenticated || isAuthenticating || showingError
+
+    private fun shouldAnimateCoexIconView(
+        isAuthenticated: Boolean,
+        isAuthenticating: Boolean,
+        isPendingConfirmation: Boolean,
+        showingError: Boolean
+    ) =
+        (isAuthenticating && _previousIconWasError.value) ||
+            isPendingConfirmation ||
+            isAuthenticated ||
+            showingError
+
+    /** Whether the current iconOverlayAsset animation should be playing. */
+    val shouldAnimateIconOverlay: Flow<Boolean> =
+        activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+            when (activeAuthType) {
+                AuthType.Fingerprint,
+                AuthType.Coex ->
+                    combine(
+                        promptSelectorInteractor.sensorType,
+                        promptViewModel.isAuthenticated,
+                        promptViewModel.isAuthenticating,
+                        promptViewModel.showingError
+                    ) {
+                        sensorType: FingerprintSensorType,
+                        authState: PromptAuthState,
+                        isAuthenticating: Boolean,
+                        showingError: Boolean ->
+                        when (sensorType) {
+                            FingerprintSensorType.POWER_BUTTON ->
+                                shouldAnimateSfpsIconOverlay(
+                                    authState.isAuthenticated,
+                                    isAuthenticating,
+                                    showingError
+                                )
+                            else -> false
+                        }
+                    }
+                AuthType.Face -> flowOf(false)
+            }
+        }
+
+    private fun shouldAnimateSfpsIconOverlay(
+        isAuthenticated: Boolean,
+        isAuthenticating: Boolean,
+        showingError: Boolean
+    ) = (isAuthenticating && _previousIconOverlayWasError.value) || isAuthenticated || showingError
+
+    /** Whether the iconView should be flipped due to a device using reverse default rotation . */
+    val shouldFlipIconView: Flow<Boolean> =
+        activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+            when (activeAuthType) {
+                AuthType.Fingerprint,
+                AuthType.Coex ->
+                    combine(
+                        promptSelectorInteractor.sensorType,
+                        displayStateInteractor.currentRotation
+                    ) { sensorType: FingerprintSensorType, rotation: DisplayRotation ->
+                        when (sensorType) {
+                            FingerprintSensorType.POWER_BUTTON ->
+                                (rotation == DisplayRotation.ROTATION_180)
+                            else -> false
+                        }
+                    }
+                AuthType.Face -> flowOf(false)
+            }
+        }
+
+    /** Whether the current BiometricPromptLayout.iconView asset animation should be repeated. */
+    val shouldRepeatAnimation: Flow<Boolean> =
+        activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+            when (activeAuthType) {
+                AuthType.Fingerprint,
+                AuthType.Coex -> flowOf(false)
+                AuthType.Face -> promptViewModel.isAuthenticating.map { it }
+            }
+        }
+
+    /** Called on configuration changes */
+    fun onConfigurationChanged(newConfig: Configuration) {
+        displayStateInteractor.onConfigurationChanged(newConfig)
+    }
+
+    /** iconView assets for caching */
+    fun getRawAssets(hasSfps: Boolean): List<Int> {
+        return if (hasSfps) {
+            listOf(
+                R.raw.biometricprompt_fingerprint_to_error_landscape,
+                R.raw.biometricprompt_folded_base_bottomright,
+                R.raw.biometricprompt_folded_base_default,
+                R.raw.biometricprompt_folded_base_topleft,
+                R.raw.biometricprompt_landscape_base,
+                R.raw.biometricprompt_portrait_base_bottomright,
+                R.raw.biometricprompt_portrait_base_topleft,
+                R.raw.biometricprompt_symbol_error_to_fingerprint_landscape,
+                R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright,
+                R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft,
+                R.raw.biometricprompt_symbol_error_to_success_landscape,
+                R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright,
+                R.raw.biometricprompt_symbol_error_to_success_portrait_topleft,
+                R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright,
+                R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft,
+                R.raw.biometricprompt_symbol_fingerprint_to_success_landscape,
+                R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright,
+                R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
+            )
+        } else {
+            listOf(
+                R.raw.fingerprint_dialogue_error_to_fingerprint_lottie,
+                R.raw.fingerprint_dialogue_error_to_success_lottie,
+                R.raw.fingerprint_dialogue_fingerprint_to_error_lottie,
+                R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 267afae..e49b4a7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -28,10 +28,10 @@
 import com.android.systemui.biometrics.shared.model.BiometricModality
 import com.android.systemui.biometrics.shared.model.DisplayRotation
 import com.android.systemui.biometrics.shared.model.PromptKind
-import com.android.systemui.biometrics.ui.binder.Spaghetti
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.VibratorHelper
 import javax.inject.Inject
 import kotlinx.coroutines.Job
@@ -39,7 +39,6 @@
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
@@ -51,25 +50,29 @@
 class PromptViewModel
 @Inject
 constructor(
-    private val displayStateInteractor: DisplayStateInteractor,
-    private val promptSelectorInteractor: PromptSelectorInteractor,
+    displayStateInteractor: DisplayStateInteractor,
+    promptSelectorInteractor: PromptSelectorInteractor,
     private val vibrator: VibratorHelper,
     @Application context: Context,
     private val featureFlags: FeatureFlags,
 ) {
-    /** Models UI of [BiometricPromptLayout.iconView] */
-    val fingerprintIconViewModel: PromptFingerprintIconViewModel =
-        PromptFingerprintIconViewModel(displayStateInteractor, promptSelectorInteractor)
-
     /** The set of modalities available for this prompt */
     val modalities: Flow<BiometricModalities> =
         promptSelectorInteractor.prompt
             .map { it?.modalities ?: BiometricModalities() }
             .distinctUntilChanged()
 
-    // TODO(b/251476085): remove after icon controllers are migrated - do not keep this state
-    private var _legacyState = MutableStateFlow(Spaghetti.BiometricState.STATE_IDLE)
-    val legacyState: StateFlow<Spaghetti.BiometricState> = _legacyState.asStateFlow()
+    /** Layout params for fingerprint iconView */
+    val fingerprintIconWidth: Int =
+        context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_fingerprint_icon_width)
+    val fingerprintIconHeight: Int =
+        context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_fingerprint_icon_height)
+
+    /** Layout params for face iconView */
+    val faceIconWidth: Int =
+        context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
+    val faceIconHeight: Int =
+        context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
 
     private val _isAuthenticating: MutableStateFlow<Boolean> = MutableStateFlow(false)
 
@@ -82,6 +85,12 @@
     /** If the user has successfully authenticated and confirmed (when explicitly required). */
     val isAuthenticated: Flow<PromptAuthState> = _isAuthenticated.asStateFlow()
 
+    /** If the auth is pending confirmation. */
+    val isPendingConfirmation: Flow<Boolean> =
+        isAuthenticated.map { authState ->
+            authState.isAuthenticated && authState.needsUserConfirmation
+        }
+
     private val _isOverlayTouched: MutableStateFlow<Boolean> = MutableStateFlow(false)
 
     /** The kind of credential the user has. */
@@ -96,6 +105,9 @@
     /** A message to show the user, if there is an error, hint, or help to show. */
     val message: Flow<PromptMessage> = _message.asStateFlow()
 
+    /** Whether an error message is currently being shown. */
+    val showingError: Flow<Boolean> = message.map { it.isError }.distinctUntilChanged()
+
     private val isRetrySupported: Flow<Boolean> = modalities.map { it.hasFace }
 
     private val _fingerprintStartMode = MutableStateFlow(FingerprintStartMode.Pending)
@@ -141,6 +153,38 @@
             !isOverlayTouched && size.isNotSmall
         }
 
+    /**
+     * When fingerprint and face modalities are enrolled, indicates whether only face auth has
+     * started.
+     *
+     * True when fingerprint and face modalities are enrolled and implicit flow is active. This
+     * occurs in co-ex auth when confirmation is not required and only face auth is started, then
+     * becomes false when device transitions to explicit flow after a first error, when the
+     * fingerprint sensor is started.
+     *
+     * False when the dialog opens in explicit flow (fingerprint and face modalities enrolled but
+     * confirmation is required), or if user has only fingerprint enrolled, or only face enrolled.
+     */
+    val faceMode: Flow<Boolean> =
+        combine(modalities, isConfirmationRequired, fingerprintStartMode) {
+                modalities: BiometricModalities,
+                isConfirmationRequired: Boolean,
+                fingerprintStartMode: FingerprintStartMode ->
+                if (modalities.hasFaceAndFingerprint) {
+                    if (isConfirmationRequired) {
+                        false
+                    } else {
+                        !fingerprintStartMode.isStarted
+                    }
+                } else {
+                    false
+                }
+            }
+            .distinctUntilChanged()
+
+    val iconViewModel: PromptIconViewModel =
+        PromptIconViewModel(this, displayStateInteractor, promptSelectorInteractor)
+
     /** Padding for prompt UI elements */
     val promptPadding: Flow<Rect> =
         combine(size, displayStateInteractor.currentRotation) { size, rotation ->
@@ -184,9 +228,9 @@
     val isConfirmButtonVisible: Flow<Boolean> =
         combine(
                 size,
-                isAuthenticated,
-            ) { size, authState ->
-                size.isNotSmall && authState.isAuthenticated && authState.needsUserConfirmation
+                isPendingConfirmation,
+            ) { size, isPendingConfirmation ->
+                size.isNotSmall && isPendingConfirmation
             }
             .distinctUntilChanged()
 
@@ -293,7 +337,6 @@
         _isAuthenticated.value = PromptAuthState(false)
         _forceMediumSize.value = true
         _message.value = PromptMessage.Error(message)
-        _legacyState.value = Spaghetti.BiometricState.STATE_ERROR
 
         if (hapticFeedback) {
             vibrator.error(failedModality)
@@ -305,7 +348,7 @@
             if (authenticateAfterError) {
                 showAuthenticating(messageAfterError)
             } else {
-                showInfo(messageAfterError)
+                showHelp(messageAfterError)
             }
         }
     }
@@ -325,15 +368,12 @@
     private fun supportsRetry(failedModality: BiometricModality) =
         failedModality == BiometricModality.Face
 
-    suspend fun showHelp(message: String) = showHelp(message, clearIconError = false)
-    suspend fun showInfo(message: String) = showHelp(message, clearIconError = true)
-
     /**
      * Show a persistent help message.
      *
      * Will be show even if the user has already authenticated.
      */
-    private suspend fun showHelp(message: String, clearIconError: Boolean) {
+    suspend fun showHelp(message: String) {
         val alreadyAuthenticated = _isAuthenticated.value.isAuthenticated
         if (!alreadyAuthenticated) {
             _isAuthenticating.value = false
@@ -343,16 +383,6 @@
         _message.value =
             if (message.isNotBlank()) PromptMessage.Help(message) else PromptMessage.Empty
         _forceMediumSize.value = true
-        _legacyState.value =
-            if (alreadyAuthenticated && isConfirmationRequired.first()) {
-                Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION
-            } else if (alreadyAuthenticated && !isConfirmationRequired.first()) {
-                Spaghetti.BiometricState.STATE_AUTHENTICATED
-            } else if (clearIconError) {
-                Spaghetti.BiometricState.STATE_IDLE
-            } else {
-                Spaghetti.BiometricState.STATE_HELP
-            }
 
         messageJob?.cancel()
         messageJob = null
@@ -376,7 +406,6 @@
         _message.value =
             if (message.isNotBlank()) PromptMessage.Help(message) else PromptMessage.Empty
         _forceMediumSize.value = true
-        _legacyState.value = Spaghetti.BiometricState.STATE_HELP
 
         messageJob?.cancel()
         messageJob = launch {
@@ -396,7 +425,6 @@
         _isAuthenticating.value = true
         _isAuthenticated.value = PromptAuthState(false)
         _message.value = if (message.isBlank()) PromptMessage.Empty else PromptMessage.Help(message)
-        _legacyState.value = Spaghetti.BiometricState.STATE_AUTHENTICATING
 
         // reset the try again button(s) after the user attempts a retry
         if (isRetry) {
@@ -427,12 +455,6 @@
         _isAuthenticated.value =
             PromptAuthState(true, modality, needsUserConfirmation, dismissAfterDelay)
         _message.value = PromptMessage.Empty
-        _legacyState.value =
-            if (needsUserConfirmation) {
-                Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION
-            } else {
-                Spaghetti.BiometricState.STATE_AUTHENTICATED
-            }
 
         if (!needsUserConfirmation) {
             vibrator.success(modality)
@@ -472,7 +494,6 @@
 
         _isAuthenticated.value = authState.asExplicitlyConfirmed()
         _message.value = PromptMessage.Empty
-        _legacyState.value = Spaghetti.BiometricState.STATE_AUTHENTICATED
 
         vibrator.success(authState.authenticatedModality)
 
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
index 2bd6258..21578f4 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
@@ -146,6 +146,16 @@
     /** Show the bouncer if necessary and set the relevant states. */
     @JvmOverloads
     fun show(isScrimmed: Boolean) {
+        if (primaryBouncerView.delegate == null) {
+            Log.d(
+                TAG,
+                "PrimaryBouncerInteractor#show is being called before the " +
+                    "primaryBouncerDelegate is set. Let's exit early so we don't set the wrong " +
+                    "primaryBouncer state."
+            )
+            return
+        }
+
         // Reset some states as we show the bouncer.
         repository.setKeyguardAuthenticatedBiometrics(null)
         repository.setPrimaryStartingToHide(false)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt
index f9c4f29..1a214ba 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.communal.data.model
 
-import com.android.systemui.communal.shared.CommunalContentSize
+import com.android.systemui.communal.shared.model.CommunalContentSize
 
 /** Metadata for the default widgets */
 data class CommunalWidgetMetadata(
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index f13b62f..77025dc 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -20,6 +20,7 @@
 import android.appwidget.AppWidgetManager
 import android.appwidget.AppWidgetProviderInfo
 import android.content.BroadcastReceiver
+import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
@@ -28,11 +29,12 @@
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.communal.data.model.CommunalWidgetMetadata
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
-import com.android.systemui.communal.shared.CommunalContentSize
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
@@ -43,6 +45,7 @@
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 
 /** Encapsulates the state of widgets for communal mode. */
@@ -52,6 +55,9 @@
 
     /** Widgets that are allowed to render in the glanceable hub */
     val communalWidgetAllowlist: List<CommunalWidgetMetadata>
+
+    /** A flow of information about all the communal widgets to show. */
+    val communalWidgets: Flow<List<CommunalWidgetContentModel>>
 }
 
 @SysUISingleton
@@ -67,7 +73,7 @@
     private val userManager: UserManager,
     private val userTracker: UserTracker,
     @CommunalLog logBuffer: LogBuffer,
-    featureFlags: FeatureFlags,
+    featureFlags: FeatureFlagsClassic,
 ) : CommunalWidgetRepository {
     companion object {
         const val TAG = "CommunalWidgetRepository"
@@ -88,49 +94,59 @@
     // Widgets that should be rendered in communal mode.
     private val widgets: HashMap<Int, CommunalAppWidgetInfo> = hashMapOf()
 
-    private val isUserUnlocked: Flow<Boolean> = callbackFlow {
-        if (!featureFlags.isEnabled(Flags.WIDGET_ON_KEYGUARD)) {
-            awaitClose()
-        }
-
-        fun isUserUnlockingOrUnlocked(): Boolean {
-            return userManager.isUserUnlockingOrUnlocked(userTracker.userHandle)
-        }
-
-        fun send() {
-            trySendWithFailureLogging(isUserUnlockingOrUnlocked(), TAG)
-        }
-
-        if (isUserUnlockingOrUnlocked()) {
-            send()
-            awaitClose()
-        } else {
-            val receiver =
-                object : BroadcastReceiver() {
-                    override fun onReceive(context: Context?, intent: Intent?) {
-                        send()
-                    }
+    private val isUserUnlocked: Flow<Boolean> =
+        callbackFlow {
+                if (!communalRepository.isCommunalEnabled) {
+                    awaitClose()
                 }
 
-            broadcastDispatcher.registerReceiver(
-                receiver,
-                IntentFilter(Intent.ACTION_USER_UNLOCKED),
-            )
+                fun isUserUnlockingOrUnlocked(): Boolean {
+                    return userManager.isUserUnlockingOrUnlocked(userTracker.userHandle)
+                }
 
-            awaitClose { broadcastDispatcher.unregisterReceiver(receiver) }
+                fun send() {
+                    trySendWithFailureLogging(isUserUnlockingOrUnlocked(), TAG)
+                }
+
+                if (isUserUnlockingOrUnlocked()) {
+                    send()
+                    awaitClose()
+                } else {
+                    val receiver =
+                        object : BroadcastReceiver() {
+                            override fun onReceive(context: Context?, intent: Intent?) {
+                                send()
+                            }
+                        }
+
+                    broadcastDispatcher.registerReceiver(
+                        receiver,
+                        IntentFilter(Intent.ACTION_USER_UNLOCKED),
+                    )
+
+                    awaitClose { broadcastDispatcher.unregisterReceiver(receiver) }
+                }
+            }
+            .distinctUntilChanged()
+
+    private val isHostActive: Flow<Boolean> =
+        isUserUnlocked.map {
+            if (it) {
+                startListening()
+                true
+            } else {
+                stopListening()
+                clearWidgets()
+                false
+            }
         }
-    }
 
     override val stopwatchAppWidgetInfo: Flow<CommunalAppWidgetInfo?> =
-        isUserUnlocked.map { isUserUnlocked ->
-            if (!isUserUnlocked) {
-                clearWidgets()
-                stopListening()
+        isHostActive.map { isHostActive ->
+            if (!isHostActive || !featureFlags.isEnabled(Flags.WIDGET_ON_KEYGUARD)) {
                 return@map null
             }
 
-            startListening()
-
             val providerInfo =
                 appWidgetManager.installedProviders.find {
                     it.loadLabel(packageManager).equals(WIDGET_LABEL)
@@ -144,6 +160,42 @@
             return@map addWidget(providerInfo)
         }
 
+    override val communalWidgets: Flow<List<CommunalWidgetContentModel>> =
+        isHostActive.map { isHostActive ->
+            if (!isHostActive) {
+                return@map emptyList()
+            }
+
+            // The allowlist should be fetched from the local database with all the metadata tied to
+            // a widget, including an appWidgetId if it has been bound. Before the database is set
+            // up, we are going to use the app widget host as the source of truth for bound widgets,
+            // and rebind each time on boot.
+
+            // Remove all previously bound widgets.
+            appWidgetHost.appWidgetIds.forEach { appWidgetHost.deleteAppWidgetId(it) }
+
+            val inventory = mutableListOf<CommunalWidgetContentModel>()
+
+            // Bind all widgets from the allowlist.
+            communalWidgetAllowlist.forEach {
+                val id = appWidgetHost.allocateAppWidgetId()
+                appWidgetManager.bindAppWidgetId(
+                    id,
+                    ComponentName.unflattenFromString(it.componentName),
+                )
+
+                inventory.add(
+                    CommunalWidgetContentModel(
+                        appWidgetId = id,
+                        providerInfo = appWidgetManager.getAppWidgetInfo(id),
+                        priority = it.priority,
+                    )
+                )
+            }
+
+            return@map inventory.toList()
+        }
+
     private fun getWidgetAllowlist(): List<CommunalWidgetMetadata> {
         val componentNames =
             applicationContext.resources.getStringArray(R.array.config_communalWidgetAllowlist)
@@ -151,7 +203,7 @@
             CommunalWidgetMetadata(
                 componentName = name,
                 priority = componentNames.size - index,
-                sizes = listOf(CommunalContentSize.HALF)
+                sizes = listOf(CommunalContentSize.HALF),
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 04bb6ae..6238707 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -18,7 +18,8 @@
 
 import com.android.systemui.communal.data.repository.CommunalRepository
 import com.android.systemui.communal.data.repository.CommunalWidgetRepository
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import com.android.systemui.dagger.SysUISingleton
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
@@ -38,4 +39,12 @@
 
     /** A flow of info about the widget to be displayed, or null if widget is unavailable. */
     val appWidgetInfo: Flow<CommunalAppWidgetInfo?> = widgetRepository.stopwatchAppWidgetInfo
+
+    /**
+     * A flow of information about widgets to be shown in communal hub.
+     *
+     * Currently only showing persistent widgets that have been bound to the app widget service
+     * (have an allocated id).
+     */
+    val widgetContent: Flow<List<CommunalWidgetContentModel>> = widgetRepository.communalWidgets
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt
deleted file mode 100644
index 0bd7d86..0000000
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.android.systemui.communal.shared
-
-/** Supported sizes for communal content in the layout grid. */
-enum class CommunalContentSize {
-    FULL,
-    HALF,
-    THIRD,
-}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalAppWidgetInfo.kt
similarity index 94%
rename from packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
rename to packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalAppWidgetInfo.kt
index 0803a01..109ed2d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalAppWidgetInfo.kt
@@ -15,7 +15,7 @@
  *
  */
 
-package com.android.systemui.communal.shared
+package com.android.systemui.communal.shared.model
 
 import android.appwidget.AppWidgetProviderInfo
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentCategory.kt
similarity index 67%
copy from packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
copy to packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentCategory.kt
index 0803a01..7f05b9c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentCategory.kt
@@ -12,15 +12,14 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
- *
  */
 
-package com.android.systemui.communal.shared
+package com.android.systemui.communal.shared.model
 
-import android.appwidget.AppWidgetProviderInfo
+enum class CommunalContentCategory {
+    /** The content persists in the communal hub until removed by the user. */
+    PERSISTENT,
 
-/** A data class that stores info about an app widget that displays in communal mode. */
-data class CommunalAppWidgetInfo(
-    val providerInfo: AppWidgetProviderInfo,
-    val appWidgetId: Int,
-)
+    /** The content temporarily shows up in the communal hub when certain conditions are met. */
+    TRANSIENT,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
copy to packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
index 0803a01..39a6476 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
@@ -12,15 +12,18 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
- *
  */
 
-package com.android.systemui.communal.shared
+package com.android.systemui.communal.shared.model
 
-import android.appwidget.AppWidgetProviderInfo
+/** Supported sizes for communal content in the layout grid. */
+enum class CommunalContentSize {
+    /** Content takes the full height of the column. */
+    FULL,
 
-/** A data class that stores info about an app widget that displays in communal mode. */
-data class CommunalAppWidgetInfo(
-    val providerInfo: AppWidgetProviderInfo,
-    val appWidgetId: Int,
-)
+    /** Content takes half of the height of the column. */
+    HALF,
+
+    /** Content takes a third of the height of the column. */
+    THIRD,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
similarity index 80%
copy from packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
copy to packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
index 0803a01..e141dc4 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
@@ -12,15 +12,15 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
- *
  */
 
-package com.android.systemui.communal.shared
+package com.android.systemui.communal.shared.model
 
 import android.appwidget.AppWidgetProviderInfo
 
-/** A data class that stores info about an app widget that displays in communal mode. */
-data class CommunalAppWidgetInfo(
-    val providerInfo: AppWidgetProviderInfo,
+/** Encapsulates data for a communal widget. */
+data class CommunalWidgetContentModel(
     val appWidgetId: Int,
+    val providerInfo: AppWidgetProviderInfo,
+    val priority: Int,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt
index 2a08d7f..0daf7b5 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt
@@ -20,7 +20,7 @@
 import android.appwidget.AppWidgetManager
 import android.content.Context
 import android.util.SizeF
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
 import com.android.systemui.communal.ui.view.CommunalWidgetWrapper
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.log.LogBuffer
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/model/CommunalContentUiModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/model/CommunalContentUiModel.kt
new file mode 100644
index 0000000..98060dc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/model/CommunalContentUiModel.kt
@@ -0,0 +1,15 @@
+package com.android.systemui.communal.ui.model
+
+import android.view.View
+import com.android.systemui.communal.shared.model.CommunalContentSize
+
+/**
+ * Encapsulates data for a communal content that holds a view.
+ *
+ * This model stays in the UI layer.
+ */
+data class CommunalContentUiModel(
+    val view: View,
+    val size: CommunalContentSize,
+    val priority: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index ddeb1d6..25c64ea 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -16,17 +16,43 @@
 
 package com.android.systemui.communal.ui.viewmodel
 
+import android.appwidget.AppWidgetHost
+import android.content.Context
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
+import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.ui.model.CommunalContentUiModel
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
 
 @SysUISingleton
 class CommunalViewModel
 @Inject
 constructor(
+    @Application private val context: Context,
+    private val appWidgetHost: AppWidgetHost,
+    communalInteractor: CommunalInteractor,
     tutorialInteractor: CommunalTutorialInteractor,
 ) {
     /** Whether communal hub should show tutorial content. */
     val showTutorialContent: Flow<Boolean> = tutorialInteractor.isTutorialAvailable
+
+    /** List of widgets to be displayed in the communal hub. */
+    val widgetContent: Flow<List<CommunalContentUiModel>> =
+        communalInteractor.widgetContent.map {
+            it.map {
+                // TODO(b/306406256): As adding and removing widgets functionalities are
+                // supported, cache the host views so they're not recreated each time.
+                val hostView = appWidgetHost.createView(context, it.appWidgetId, it.providerInfo)
+                return@map CommunalContentUiModel(
+                    view = hostView,
+                    priority = it.priority,
+                    // All widgets have HALF size.
+                    size = CommunalContentSize.HALF,
+                )
+            }
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalWidgetViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalWidgetViewModel.kt
index 8fba342..d7bbea6 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalWidgetViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalWidgetViewModel.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.communal.ui.viewmodel
 
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
index 9221832b..4bdea75 100644
--- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
+++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
@@ -79,4 +79,7 @@
         context: Context,
         viewModel: CommunalViewModel,
     ): View
+
+    /** Creates a container that hosts the communal UI and handles gesture transitions. */
+    fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index ecd8b0e..c91c9ac 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -776,7 +776,7 @@
     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/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index 84cd3ef..3eef6aa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import java.util.UUID
@@ -48,8 +49,8 @@
  * [TransitionInteractor]. These interactors will call [startTransition] and [updateTransition] on
  * this repository.
  *
- * To print all transitions to logcat to help with debugging, run this command:
- * adb shell settings put global systemui/buffer/KeyguardLog VERBOSE
+ * To print all transitions to logcat to help with debugging, run this command: adb shell settings
+ * put global systemui/buffer/KeyguardLog VERBOSE
  *
  * This will print all keyguard transitions to logcat with the KeyguardTransitionAuditLogger tag.
  */
@@ -73,11 +74,8 @@
     /**
      * Begin a transition from one state to another. Transitions are interruptible, and will issue a
      * [TransitionStep] with state = [TransitionState.CANCELED] before beginning the next one.
-     *
-     * When canceled, there are two options: to continue from the current position of the prior
-     * transition, or to reset the position. When [resetIfCanceled] == true, it will do the latter.
      */
-    fun startTransition(info: TransitionInfo, resetIfCanceled: Boolean = false): UUID?
+    fun startTransition(info: TransitionInfo): UUID?
 
     /**
      * Allows manual control of a transition. When calling [startTransition], the consumer must pass
@@ -138,10 +136,7 @@
         )
     }
 
-    override fun startTransition(
-        info: TransitionInfo,
-        resetIfCanceled: Boolean,
-    ): UUID? {
+    override fun startTransition(info: TransitionInfo): UUID? {
         if (lastStep.from == info.from && lastStep.to == info.to) {
             Log.i(TAG, "Duplicate call to start the transition, rejecting: $info")
             return null
@@ -149,10 +144,10 @@
         val startingValue =
             if (lastStep.transitionState != TransitionState.FINISHED) {
                 Log.i(TAG, "Transition still active: $lastStep, canceling")
-                if (resetIfCanceled) {
-                    0f
-                } else {
-                    lastStep.value
+                when (info.modeOnCanceled) {
+                    TransitionModeOnCanceled.LAST_VALUE -> lastStep.value
+                    TransitionModeOnCanceled.RESET -> 0f
+                    TransitionModeOnCanceled.REVERSE -> 1f - lastStep.value
                 }
             } else {
                 0f
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 6e0aa4c..a331a66 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
@@ -65,8 +66,21 @@
                 )
                 .collect { (_, lastStartedStep, occluded) ->
                     if (lastStartedStep.to == KeyguardState.AOD) {
-                        startTransitionTo(
+                        val toState =
                             if (occluded) KeyguardState.OCCLUDED else KeyguardState.LOCKSCREEN
+                        val modeOnCanceled =
+                            if (
+                                toState == KeyguardState.LOCKSCREEN &&
+                                    lastStartedStep.from == KeyguardState.LOCKSCREEN
+                            ) {
+                                TransitionModeOnCanceled.REVERSE
+                            } else {
+                                TransitionModeOnCanceled.LAST_VALUE
+                            }
+
+                        startTransitionTo(
+                            toState = toState,
+                            modeOnCanceled = modeOnCanceled,
                         )
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index c67153a..eace0c7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
@@ -114,8 +115,9 @@
                 .collect { (isAsleep, lastStartedStep, isAodAvailable) ->
                     if (lastStartedStep.to == KeyguardState.GONE && isAsleep) {
                         startTransitionTo(
-                            if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING,
-                            resetIfCancelled = true,
+                            toState =
+                                if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING,
+                            modeOnCanceled = TransitionModeOnCanceled.RESET,
                         )
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index c39a4c9..d44a9d8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
 import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
 import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.shade.data.repository.ShadeRepository
@@ -340,8 +341,20 @@
                 )
                 .collect { (isAsleep, lastStartedStep, isAodAvailable) ->
                     if (lastStartedStep.to == KeyguardState.LOCKSCREEN && isAsleep) {
-                        startTransitionTo(
+                        val toState =
                             if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
+                        val modeOnCanceled =
+                            if (
+                                toState == KeyguardState.AOD &&
+                                    lastStartedStep.from == KeyguardState.AOD
+                            ) {
+                                TransitionModeOnCanceled.REVERSE
+                            } else {
+                                TransitionModeOnCanceled.LAST_VALUE
+                            }
+                        startTransitionTo(
+                            toState = toState,
+                            modeOnCanceled = modeOnCanceled,
                         )
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index f1649fb..24b6661 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.kotlin.Utils.Companion.toQuad
@@ -236,7 +237,7 @@
                                 getDefaultAnimatorForTransitionsToState(KeyguardState.GONE).apply {
                                     this.duration = duration.inWholeMilliseconds
                                 },
-                            resetIfCancelled = true
+                            modeOnCanceled = TransitionModeOnCanceled.RESET,
                         )
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index 54c6d5f..7601808 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.util.kotlin.sample
 import java.util.UUID
 import kotlinx.coroutines.CoroutineScope
@@ -49,7 +50,7 @@
     fun startTransitionTo(
         toState: KeyguardState,
         animator: ValueAnimator? = getDefaultAnimatorForTransitionsToState(toState),
-        resetIfCancelled: Boolean = false,
+        modeOnCanceled: TransitionModeOnCanceled = TransitionModeOnCanceled.LAST_VALUE
     ): UUID? {
         if (
             fromState != transitionInteractor.startedKeyguardState.value &&
@@ -73,8 +74,8 @@
                 fromState,
                 toState,
                 animator,
-            ),
-            resetIfCancelled
+                modeOnCanceled,
+            )
         )
     }
 
@@ -91,8 +92,8 @@
                     // so use the last finishedKeyguardState to determine the overriding FROM state
                     if (finishedKeyguardState == fromState) {
                         startTransitionTo(
-                            KeyguardState.OCCLUDED,
-                            resetIfCancelled = true,
+                            toState = KeyguardState.OCCLUDED,
+                            modeOnCanceled = TransitionModeOnCanceled.RESET,
                         )
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt
index bfccf3fe..7a37365 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt
@@ -22,7 +22,13 @@
     val ownerName: String,
     val from: KeyguardState,
     val to: KeyguardState,
-    val animator: ValueAnimator?, // 'null' animator signal manual control
+    /** [null] animator signals manual control, otherwise transition run by the animator */
+    val animator: ValueAnimator?,
+    /**
+     * If the transition resets in the cancellation of another transition, use this mode to
+     * determine how to continue.
+     */
+    val modeOnCanceled: TransitionModeOnCanceled = TransitionModeOnCanceled.LAST_VALUE,
 ) {
     override fun toString(): String =
         "TransitionInfo(ownerName=$ownerName, from=$from, to=$to, " +
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionModeOnCanceled.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionModeOnCanceled.kt
new file mode 100644
index 0000000..56f90bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionModeOnCanceled.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+/** When canceled, provide different ways to start the next transition. */
+enum class TransitionModeOnCanceled {
+    /** Proceed from the last value. If canceled at .7, start from .7 and end at 1 */
+    LAST_VALUE,
+    /** Start over from 0. If canceled at .7, start from 0 and end at 1 */
+    RESET,
+    /** Reverse the transition. If canceled at .7, start from 1-.7 (0.3) and end at 1 */
+    REVERSE
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 67531ad..fd6b3f1 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -190,17 +190,6 @@
         }
     }
 
-    /**
-     * Provides a logging buffer for all logs related to Quick Settings tiles. This LogBuffer is
-     * unique for each tile.
-     * go/qs-tile-refactor
-     */
-    @Provides
-    @QSTilesDefaultLog
-    public static LogBuffer provideQuickSettingsTilesLogBuffer(LogBufferFactory factory) {
-        return factory.create("QSTileLog", 25 /* maxSize */, false /* systrace */);
-    }
-
     @Provides
     @QSTilesLogBuffers
     public static Map<TileSpec, LogBuffer> provideQuickSettingsTilesLogBufferCache() {
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesDefaultLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesDefaultLog.kt
deleted file mode 100644
index 6575cdd..0000000
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesDefaultLog.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.log.dagger
-
-import javax.inject.Qualifier
-
-/**
- * A default [com.android.systemui.log.LogBuffer] for QS tiles messages. It's used exclusively in
- * [com.android.systemui.qs.tiles.base.logging.QSTileLogger]. If you need to increase it for you
- * tile, add one to the map provided by the [QSTilesLogBuffers]
- */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class QSTilesDefaultLog
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index a48e56a..7cb5b3b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -731,6 +731,7 @@
                     removePlayer(existingSmartspaceMediaKey, dismissMediaData = false)
                 removedPlayer?.run {
                     debugLogger.logPotentialMemoryLeak(existingSmartspaceMediaKey)
+                    onDestroy()
                 }
             }
 
@@ -1302,6 +1303,7 @@
         val removedPlayer = removeMediaPlayer(key)
         if (removedPlayer != null && removedPlayer != player) {
             debugLogger?.logPotentialMemoryLeak(key)
+            removedPlayer.onDestroy()
         }
         val sortKey =
             MediaSortKey(
@@ -1329,6 +1331,7 @@
         val removedPlayer = removeMediaPlayer(key)
         if (!update && removedPlayer != null && removedPlayer != player) {
             debugLogger?.logPotentialMemoryLeak(key)
+            removedPlayer.onDestroy()
         }
         val sortKey =
             MediaSortKey(
@@ -1357,7 +1360,10 @@
             // MediaPlayer should not be visible
             // no need to set isDismissed flag.
             val removedPlayer = removeMediaPlayer(newKey)
-            removedPlayer?.run { debugLogger?.logPotentialMemoryLeak(newKey) }
+            removedPlayer?.run {
+                debugLogger?.logPotentialMemoryLeak(newKey)
+                onDestroy()
+            }
             mediaData.put(newKey, it)
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
index a53f0f1..ce8b79c 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
@@ -24,6 +24,7 @@
 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
 
@@ -47,6 +48,10 @@
         )
     }
 
+    fun notifyPermissionRequestDisplayed() {
+        notifyToServer(METRICS_STATE_PERMISSION_REQUEST_DISPLAYED, SessionCreationSource.UNKNOWN)
+    }
+
     /**
      * Request to log that the permission request moved to the given state.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index fa418fc..2d830d3 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -249,6 +249,10 @@
 
         setUpDialog(mDialog);
         mDialog.show();
+
+        if (savedInstanceState == null) {
+            mMediaProjectionMetricsLogger.notifyPermissionRequestDisplayed();
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index 959afd8..e27a59c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -30,12 +30,12 @@
 
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
 import com.android.systemui.animation.DialogCuj;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.qs.QSTile;
@@ -45,6 +45,7 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
 import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -69,6 +70,7 @@
     private final DialogLaunchAnimator mDialogLaunchAnimator;
     private final FeatureFlags mFlags;
     private final PanelInteractor mPanelInteractor;
+    private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
 
     private long mMillisUntilFinished = 0;
 
@@ -88,7 +90,8 @@
             KeyguardDismissUtil keyguardDismissUtil,
             KeyguardStateController keyguardStateController,
             DialogLaunchAnimator dialogLaunchAnimator,
-            PanelInteractor panelInteractor
+            PanelInteractor panelInteractor,
+            MediaProjectionMetricsLogger mediaProjectionMetricsLogger
     ) {
         super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
@@ -99,6 +102,7 @@
         mKeyguardStateController = keyguardStateController;
         mDialogLaunchAnimator = dialogLaunchAnimator;
         mPanelInteractor = panelInteractor;
+        mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;
     }
 
     @Override
@@ -190,6 +194,9 @@
             } else {
                 dialog.show();
             }
+
+            mMediaProjectionMetricsLogger.notifyPermissionRequestDisplayed();
+
             return false;
         };
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
index 1b0d5f9..2f8fe42 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
@@ -32,7 +32,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
@@ -43,6 +42,7 @@
 import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -55,7 +55,7 @@
     private final KeyguardStateController mKeyguard;
     protected IndividualSensorPrivacyController mSensorPrivacyController;
 
-    private final SafetyCenterManager mSafetyCenterManager;
+    private final Boolean mIsSafetyCenterEnabled;
 
     /**
      * @return Id of the sensor that will be toggled
@@ -89,7 +89,7 @@
                 statusBarStateController, activityStarter, qsLogger);
         mSensorPrivacyController = sensorPrivacyController;
         mKeyguard = keyguardStateController;
-        mSafetyCenterManager = safetyCenterManager;
+        mIsSafetyCenterEnabled = safetyCenterManager.isSafetyCenterEnabled();
         mSensorPrivacyController.observe(getLifecycle(), this);
     }
 
@@ -138,7 +138,7 @@
 
     @Override
     public Intent getLongClickIntent() {
-        if (mSafetyCenterManager.isSafetyCenterEnabled()) {
+        if (mIsSafetyCenterEnabled) {
             return new Intent(Settings.ACTION_PRIVACY_CONTROLS);
         } else {
             return new Intent(Settings.ACTION_PRIVACY_SETTINGS);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
index 70a683b..f8de365 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
@@ -19,8 +19,8 @@
 import androidx.annotation.GuardedBy
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
 import com.android.systemui.log.core.LogLevel
-import com.android.systemui.log.dagger.QSTilesDefaultLog
 import com.android.systemui.log.dagger.QSTilesLogBuffers
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.pipeline.shared.TileSpec
@@ -29,14 +29,13 @@
 import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
 import com.android.systemui.statusbar.StatusBarState
 import javax.inject.Inject
-import javax.inject.Provider
 
 @SysUISingleton
 class QSTileLogger
 @Inject
 constructor(
     @QSTilesLogBuffers logBuffers: Map<TileSpec, LogBuffer>,
-    @QSTilesDefaultLog private val defaultLogBufferProvider: Provider<LogBuffer>,
+    private val factory: LogBufferFactory,
     private val mStatusBarStateController: StatusBarStateController,
 ) {
     @GuardedBy("logBufferCache") private val logBufferCache = logBuffers.toMutableMap()
@@ -154,7 +153,13 @@
 
     private fun TileSpec.getLogBuffer(): LogBuffer =
         synchronized(logBufferCache) {
-            logBufferCache.getOrPut(this) { defaultLogBufferProvider.get() }
+            logBufferCache.getOrPut(this) {
+                factory.create(
+                    "QSTileLog_${this.getLogTag()}",
+                    BUFFER_MAX_SIZE /* maxSize */,
+                    false /* systrace */
+                )
+            }
         }
 
     private fun StateUpdateTrigger.toLogString(): String =
@@ -185,5 +190,6 @@
     private companion object {
         const val TAG_FORMAT_PREFIX = "QSLog"
         const val DATA_MAX_LENGTH = 50
+        const val BUFFER_MAX_SIZE = 25
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 0951440..d869239 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -35,6 +35,7 @@
 import com.android.keyguard.LockIconViewController;
 import com.android.keyguard.dagger.KeyguardBouncerComponent;
 import com.android.systemui.Dumpable;
+import com.android.systemui.FeatureFlags;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
@@ -42,10 +43,12 @@
 import com.android.systemui.bouncer.ui.binder.KeyguardBouncerViewBinder;
 import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel;
 import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel;
+import com.android.systemui.compose.ComposeFacade;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FeatureFlagsClassic;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
@@ -103,8 +106,10 @@
     private final PulsingGestureListener mPulsingGestureListener;
     private final LockscreenHostedDreamGestureListener mLockscreenHostedDreamGestureListener;
     private final NotificationInsetsController mNotificationInsetsController;
+    private final CommunalViewModel mCommunalViewModel;
     private final boolean mIsTrackpadCommonEnabled;
     private final FeatureFlags mFeatureFlags;
+    private final FeatureFlagsClassic mFeatureFlagsClassic;
     private final SysUIKeyEventHandler mSysUIKeyEventHandler;
     private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
     private final AlternateBouncerInteractor mAlternateBouncerInteractor;
@@ -175,7 +180,9 @@
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
             PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
+            CommunalViewModel communalViewModel,
             NotificationExpansionRepository notificationExpansionRepository,
+            FeatureFlagsClassic featureFlagsClassic,
             FeatureFlags featureFlags,
             SystemClock clock,
             BouncerMessageInteractor bouncerMessageInteractor,
@@ -206,8 +213,10 @@
         mPulsingGestureListener = pulsingGestureListener;
         mLockscreenHostedDreamGestureListener = lockscreenHostedDreamGestureListener;
         mNotificationInsetsController = notificationInsetsController;
-        mIsTrackpadCommonEnabled = featureFlags.isEnabled(TRACKPAD_GESTURE_COMMON);
+        mCommunalViewModel = communalViewModel;
+        mIsTrackpadCommonEnabled = featureFlagsClassic.isEnabled(TRACKPAD_GESTURE_COMMON);
         mFeatureFlags = featureFlags;
+        mFeatureFlagsClassic = featureFlagsClassic;
         mSysUIKeyEventHandler = sysUIKeyEventHandler;
         mPrimaryBouncerInteractor = primaryBouncerInteractor;
         mAlternateBouncerInteractor = alternateBouncerInteractor;
@@ -223,7 +232,7 @@
                 messageAreaControllerFactory,
                 bouncerMessageInteractor,
                 bouncerLogger,
-                featureFlags,
+                featureFlagsClassic,
                 selectedUserInteractor);
 
         collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(),
@@ -234,7 +243,7 @@
                 this::setExpandAnimationRunning);
 
         mClock = clock;
-        if (featureFlags.isEnabled(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION)) {
+        if (featureFlagsClassic.isEnabled(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION)) {
             unfoldTransitionProgressProvider.ifPresent(
                     progressProvider -> progressProvider.addCallback(
                             mDisableSubpixelTextTransitionListener));
@@ -263,7 +272,7 @@
         mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller);
         mPulsingWakeupGestureHandler = new GestureDetector(mView.getContext(),
                 mPulsingGestureListener);
-        if (mFeatureFlags.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) {
+        if (mFeatureFlagsClassic.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) {
             mDreamingWakeupGestureHandler = new GestureDetector(mView.getContext(),
                     mLockscreenHostedDreamGestureListener);
         }
@@ -435,7 +444,7 @@
                 }
 
                 boolean bouncerShowing;
-                if (mFeatureFlags.isEnabled(Flags.ALTERNATE_BOUNCER_VIEW)) {
+                if (mFeatureFlagsClassic.isEnabled(Flags.ALTERNATE_BOUNCER_VIEW)) {
                     bouncerShowing = mPrimaryBouncerInteractor.isBouncerShowing()
                             || mAlternateBouncerInteractor.isVisibleState();
                 } else {
@@ -447,7 +456,7 @@
                     if (mDragDownHelper.isDragDownEnabled()) {
                         // This handles drag down over lockscreen
                         boolean result = mDragDownHelper.onInterceptTouchEvent(ev);
-                        if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+                        if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
                             if (result) {
                                 mLastInterceptWasDragDownHelper = true;
                                 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
@@ -479,7 +488,7 @@
                 MotionEvent cancellation = MotionEvent.obtain(ev);
                 cancellation.setAction(MotionEvent.ACTION_CANCEL);
                 mStackScrollLayout.onInterceptTouchEvent(cancellation);
-                if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+                if (!mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
                     mNotificationPanelViewController.handleExternalInterceptTouch(cancellation);
                 }
                 cancellation.recycle();
@@ -494,7 +503,7 @@
                 if (mStatusBarKeyguardViewManager.onTouch(ev)) {
                     return true;
                 }
-                if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+                if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
                     if (mLastInterceptWasDragDownHelper && (mDragDownHelper.isDraggingDown())) {
                         // we still want to finish our drag down gesture when locking the screen
                         handled |= mDragDownHelper.onTouchEvent(ev) || handled;
@@ -559,8 +568,27 @@
         mDepthController.onPanelExpansionChanged(currentState);
     }
 
+    /**
+     * Sets up the communal hub UI if the {@link com.android.systemui.Flags#FLAG_COMMUNAL_HUB} flag
+     * is enabled.
+     *
+     * The layout lives in {@link R.id.communal_ui_container}.
+     */
+    public void setupCommunalHubLayout() {
+        if (!mFeatureFlags.communalHub() || !ComposeFacade.INSTANCE.isComposeAvailable()) {
+            return;
+        }
+
+        // Replace the placeholder view with the communal UI.
+        View communalPlaceholder = mView.findViewById(R.id.communal_ui_stub);
+        int index = mView.indexOfChild(communalPlaceholder);
+        mView.removeView(communalPlaceholder);
+        mView.addView(ComposeFacade.INSTANCE.createCommunalContainer(mView.getContext(),
+                mCommunalViewModel), index);
+    }
+
     private boolean didNotificationPanelInterceptEvent(MotionEvent ev) {
-        if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+        if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
             // Since NotificationStackScrollLayout is now a sibling of notification_panel, we need
             // to also ask NotificationPanelViewController directly, in order to process swipe up
             // events originating from notifications
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index a8a20cc..2f68476 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -35,7 +35,6 @@
 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 @@
     /** 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/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
index 17da015..ffde8c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
@@ -118,16 +118,27 @@
 
         // If the view is being removed, this may be called even though we're not active
         boolean remoteInputActiveForEntry = isRemoteInputActive(entry);
+        boolean remoteInputActive = isRemoteInputActive();
         mLogger.logRemoveRemoteInput(
                 entry.getKey() /* entryKey */,
                 entry.mRemoteEditImeVisible /* remoteEditImeVisible */,
                 entry.mRemoteEditImeAnimatingAway /* remoteEditImeAnimatingAway */,
                 remoteInputActiveForEntry /* isRemoteInputActiveForEntry */,
-                isRemoteInputActive()/* isRemoteInputActive */,
+                remoteInputActive/* isRemoteInputActive */,
                 reason/* reason */,
                 entry.getNotificationStyle()/* notificationStyle */);
 
-        if (!remoteInputActiveForEntry) return;
+        if (!remoteInputActiveForEntry) {
+            if (mLastAppliedRemoteInputActive != null
+                    && mLastAppliedRemoteInputActive
+                    && !remoteInputActive) {
+                mLogger.logRemoteInputApplySkipped(
+                        entry.getKey() /* entryKey */,
+                        reason/* reason */,
+                        entry.getNotificationStyle()/* notificationStyle */);
+            }
+            return;
+        }
 
         pruneWeakThenRemoveAndContains(null /* contains */, entry /* remove */, token);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
index 57d20246..8957f29 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
@@ -114,18 +114,21 @@
                 || previousAnimator.getAnimatedFraction() == 0)) {
             animator.setStartDelay(properties.delay);
         }
-        if (listener != null) {
-            animator.addListener(listener);
-        }
         // remove the tag when the animation is finished
         animator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
-                view.setTag(animatorTag, null);
-                view.setTag(animationStartTag, null);
-                view.setTag(animationEndTag, null);
+                Animator existing = (Animator) view.getTag(animatorTag);
+                if (existing == animation) {
+                    view.setTag(animatorTag, null);
+                    view.setTag(animationStartTag, null);
+                    view.setTag(animationEndTag, null);
+                }
             }
         });
+        if (listener != null) {
+            animator.addListener(listener);
+        }
         ViewState.startAnimator(animator, listener);
         view.setTag(animatorTag, animator);
         view.setTag(animationStartTag, currentValue);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
index ff89c62..23f87ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
@@ -83,6 +83,21 @@
             }
         )
 
+    fun logRemoteInputApplySkipped(entryKey: String, reason: String, notificationStyle: String) =
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                str1 = entryKey
+                str2 = reason
+                str3 = notificationStyle
+            },
+            {
+                "removeRemoteInput[apply is skipped] reason: $str2" +
+                    "for entry: $str1, style: $str3 "
+            }
+        )
+
     private companion object {
         private const val TAG = "RemoteInputControllerLog"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 4573d59..cfe9fbe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -748,7 +748,11 @@
         return row != null && row.getGuts() != null && row.getGuts().isExposed();
     }
 
-    public boolean isChildInGroup() {
+    /**
+     * @return Whether the notification row is a child of a group notification view; false if the
+     * row is null
+     */
+    public boolean rowIsChildInGroup() {
         return row != null && row.isChildInGroup();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 9fb6c1b..8d9fd12 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1525,6 +1525,7 @@
         // regressions, we'll continue standing up the root view in CentralSurfaces.
         mNotificationShadeWindowController.fetchWindowRootView();
         getNotificationShadeWindowViewController().setupExpandedStatusBar();
+        getNotificationShadeWindowViewController().setupCommunalHubLayout();
         mShadeController.setNotificationShadeWindowViewController(
                 getNotificationShadeWindowViewController());
         mBackActionInteractor.setup(mQsController, mShadeSurface);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 6b4382f73..f4862c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -175,7 +175,7 @@
         if (!hasPinnedHeadsUp() || topEntry == null) {
             return null;
         } else {
-            if (topEntry.isChildInGroup()) {
+            if (topEntry.rowIsChildInGroup()) {
                 final NotificationEntry groupSummary =
                         mGroupMembershipManager.getGroupSummary(topEntry);
                 if (groupSummary != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
index 83ff789..b3834f5 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
@@ -269,3 +269,108 @@
     crossinline getValue: () -> T,
 ): StateFlow<T> =
     changedSignals.map { getValue() }.stateIn(this, SharingStarted.Eagerly, getValue())
+
+inline fun <T1, T2, T3, T4, T5, T6, R> combine(
+        flow: Flow<T1>,
+        flow2: Flow<T2>,
+        flow3: Flow<T3>,
+        flow4: Flow<T4>,
+        flow5: Flow<T5>,
+        flow6: Flow<T6>,
+        crossinline transform: suspend (T1, T2, T3, T4, T5, T6) -> R
+): Flow<R> {
+    return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6) {
+        args: Array<*> ->
+        @Suppress("UNCHECKED_CAST")
+        transform(
+                args[0] as T1,
+                args[1] as T2,
+                args[2] as T3,
+                args[3] as T4,
+                args[4] as T5,
+                args[5] as T6
+        )
+    }
+}
+
+inline fun <T1, T2, T3, T4, T5, T6, T7, R> combine(
+        flow: Flow<T1>,
+        flow2: Flow<T2>,
+        flow3: Flow<T3>,
+        flow4: Flow<T4>,
+        flow5: Flow<T5>,
+        flow6: Flow<T6>,
+        flow7: Flow<T7>,
+        crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7) -> R
+): Flow<R> {
+    return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6, flow7) {
+        args: Array<*> ->
+        @Suppress("UNCHECKED_CAST")
+        transform(
+                args[0] as T1,
+                args[1] as T2,
+                args[2] as T3,
+                args[3] as T4,
+                args[4] as T5,
+                args[5] as T6,
+                args[6] as T7
+        )
+    }
+}
+
+inline fun <T1, T2, T3, T4, T5, T6, T7, T8, R> combine(
+        flow: Flow<T1>,
+        flow2: Flow<T2>,
+        flow3: Flow<T3>,
+        flow4: Flow<T4>,
+        flow5: Flow<T5>,
+        flow6: Flow<T6>,
+        flow7: Flow<T7>,
+        flow8: Flow<T8>,
+        crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8) -> R
+): Flow<R> {
+    return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6, flow7, flow8) {
+        args: Array<*> ->
+        @Suppress("UNCHECKED_CAST")
+        transform(
+                args[0] as T1,
+                args[1] as T2,
+                args[2] as T3,
+                args[3] as T4,
+                args[4] as T5,
+                args[5] as T6,
+                args[6] as T7,
+                args[7] as T8
+        )
+    }
+}
+
+inline fun <T1, T2, T3, T4, T5, T6, T7, T8, T9, R> combine(
+        flow: Flow<T1>,
+        flow2: Flow<T2>,
+        flow3: Flow<T3>,
+        flow4: Flow<T4>,
+        flow5: Flow<T5>,
+        flow6: Flow<T6>,
+        flow7: Flow<T7>,
+        flow8: Flow<T8>,
+        flow9: Flow<T9>,
+        crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8, T9) -> R
+): Flow<R> {
+    return kotlinx.coroutines.flow.combine(
+        flow, flow2, flow3, flow4, flow5, flow6, flow7, flow8, flow9
+    ) { args: Array<*> ->
+        @Suppress("UNCHECKED_CAST")
+        transform(
+                args[0] as T1,
+                args[1] as T2,
+                args[2] as T3,
+                args[3] as T4,
+                args[4] as T5,
+                args[5] as T6,
+                args[6] as T7,
+                args[6] as T8,
+                args[6] as T9,
+        )
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index 9dca013..aea3030 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -109,6 +109,7 @@
         private WallpaperManager mWallpaperManager;
         private final WallpaperLocalColorExtractor mWallpaperLocalColorExtractor;
         private SurfaceHolder mSurfaceHolder;
+        private boolean mDrawn = false;
         @VisibleForTesting
         static final int MIN_SURFACE_WIDTH = 128;
         @VisibleForTesting
@@ -239,6 +240,7 @@
 
         private void drawFrameSynchronized() {
             synchronized (mLock) {
+                if (mDrawn) return;
                 drawFrameInternal();
             }
         }
@@ -276,6 +278,7 @@
                 Rect dest = mSurfaceHolder.getSurfaceFrame();
                 try {
                     canvas.drawBitmap(bitmap, null, dest, null);
+                    mDrawn = true;
                 } finally {
                     surface.unlockCanvasAndPost(canvas);
                 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconControllerTest.kt
deleted file mode 100644
index 215d635..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconControllerTest.kt
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics
-
-import android.content.Context
-import android.hardware.biometrics.SensorProperties
-import android.hardware.fingerprint.FingerprintManager
-import android.hardware.fingerprint.FingerprintSensorProperties
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
-import android.view.ViewGroup.LayoutParams
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.res.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.`when` as whenEver
-import org.mockito.junit.MockitoJUnit
-
-private const val SENSOR_ID = 1
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class AuthBiometricFingerprintIconControllerTest : SysuiTestCase() {
-
-    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
-
-    @Mock private lateinit var iconView: LottieAnimationView
-    @Mock private lateinit var iconViewOverlay: LottieAnimationView
-    @Mock private lateinit var layoutParam: LayoutParams
-    @Mock private lateinit var fingerprintManager: FingerprintManager
-
-    private lateinit var controller: AuthBiometricFingerprintIconController
-
-    @Before
-    fun setUp() {
-        context.addMockSystemService(Context.FINGERPRINT_SERVICE, fingerprintManager)
-        whenEver(iconView.layoutParams).thenReturn(layoutParam)
-        whenEver(iconViewOverlay.layoutParams).thenReturn(layoutParam)
-    }
-
-    @Test
-    fun testIconContentDescription_SfpsDevice() {
-        setupFingerprintSensorProperties(FingerprintSensorProperties.TYPE_POWER_BUTTON)
-        controller = AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay)
-
-        assertThat(controller.getIconContentDescription(BiometricState.STATE_AUTHENTICATING))
-            .isEqualTo(
-                context.resources.getString(
-                    R.string.security_settings_sfps_enroll_find_sensor_message
-                )
-            )
-    }
-
-    @Test
-    fun testIconContentDescription_NonSfpsDevice() {
-        setupFingerprintSensorProperties(FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
-        controller = AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay)
-
-        assertThat(controller.getIconContentDescription(BiometricState.STATE_AUTHENTICATING))
-            .isEqualTo(context.resources.getString(R.string.fingerprint_dialog_touch_sensor))
-    }
-
-    private fun setupFingerprintSensorProperties(sensorType: Int) {
-        whenEver(fingerprintManager.sensorPropertiesInternal)
-            .thenReturn(
-                listOf(
-                    FingerprintSensorPropertiesInternal(
-                        SENSOR_ID,
-                        SensorProperties.STRENGTH_STRONG,
-                        5 /* maxEnrollmentsPerUser */,
-                        listOf() /* componentInfo */,
-                        sensorType,
-                        true /* halControlsIllumination */,
-                        true /* resetLockoutRequiresHardwareAuthToken */,
-                        listOf() /* sensorLocations */
-                    )
-                )
-            )
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
index d68a313..8c26776 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
@@ -72,6 +72,7 @@
             runCurrent()
 
             // WHEN shade expands
+            shadeRepository.setLegacyShadeTracking(true)
             shadeRepository.setLegacyShadeExpansion(.5f)
             runCurrent()
 
@@ -108,6 +109,7 @@
 
             // WHEN detector is disabled and shade opens
             detector.disable()
+            shadeRepository.setLegacyShadeTracking(true)
             shadeRepository.setLegacyShadeExpansion(.5f)
             runCurrent()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
index 9f24a9f..15633d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
@@ -30,6 +30,7 @@
 internal fun fingerprintSensorPropertiesInternal(
     ids: List<Int> = listOf(0),
     strong: Boolean = true,
+    sensorType: Int = FingerprintSensorProperties.TYPE_REAR
 ): List<FingerprintSensorPropertiesInternal> {
     val componentInfo =
         listOf(
@@ -54,7 +55,7 @@
             if (strong) SensorProperties.STRENGTH_STRONG else SensorProperties.STRENGTH_WEAK,
             5 /* maxEnrollmentsPerUser */,
             componentInfo,
-            FingerprintSensorProperties.TYPE_REAR,
+            sensorType,
             false /* resetLockoutRequiresHardwareAuthToken */
         )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt
deleted file mode 100644
index fd86486..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-package com.android.systemui.biometrics.ui.viewmodel
-
-import android.content.res.Configuration
-import androidx.test.filters.SmallTest
-import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
-import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
-import com.android.systemui.biometrics.data.repository.FakePromptRepository
-import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
-import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
-import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
-import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl
-import com.android.systemui.biometrics.shared.model.FingerprintSensorType
-import com.android.systemui.biometrics.shared.model.SensorStrength
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.display.data.repository.FakeDisplayRepository
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mock
-import org.mockito.junit.MockitoJUnit
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(JUnit4::class)
-class PromptFingerprintIconViewModelTest : SysuiTestCase() {
-
-    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
-
-    @Mock private lateinit var lockPatternUtils: LockPatternUtils
-
-    private lateinit var displayRepository: FakeDisplayRepository
-    private lateinit var fingerprintRepository: FakeFingerprintPropertyRepository
-    private lateinit var promptRepository: FakePromptRepository
-    private lateinit var displayStateRepository: FakeDisplayStateRepository
-
-    private val testScope = TestScope(StandardTestDispatcher())
-    private val fakeExecutor = FakeExecutor(FakeSystemClock())
-
-    private lateinit var promptSelectorInteractor: PromptSelectorInteractor
-    private lateinit var displayStateInteractor: DisplayStateInteractor
-    private lateinit var viewModel: PromptFingerprintIconViewModel
-
-    @Before
-    fun setup() {
-        displayRepository = FakeDisplayRepository()
-        fingerprintRepository = FakeFingerprintPropertyRepository()
-        promptRepository = FakePromptRepository()
-        displayStateRepository = FakeDisplayStateRepository()
-
-        promptSelectorInteractor =
-            PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils)
-        displayStateInteractor =
-            DisplayStateInteractorImpl(
-                testScope.backgroundScope,
-                mContext,
-                fakeExecutor,
-                displayStateRepository,
-                displayRepository,
-            )
-        viewModel = PromptFingerprintIconViewModel(displayStateInteractor, promptSelectorInteractor)
-    }
-
-    @Test
-    fun sfpsIconUpdates_onConfigurationChanged() {
-        testScope.runTest {
-            runCurrent()
-            configureFingerprintPropertyRepository(FingerprintSensorType.POWER_BUTTON)
-            val testConfig = Configuration()
-            val folded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP - 1
-            val unfolded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP + 1
-            val currentIcon = collectLastValue(viewModel.iconAsset)
-
-            testConfig.smallestScreenWidthDp = folded
-            viewModel.onConfigurationChanged(testConfig)
-            val foldedIcon = currentIcon()
-
-            testConfig.smallestScreenWidthDp = unfolded
-            viewModel.onConfigurationChanged(testConfig)
-            val unfoldedIcon = currentIcon()
-
-            assertThat(foldedIcon).isNotEqualTo(unfoldedIcon)
-        }
-    }
-
-    private fun configureFingerprintPropertyRepository(sensorType: FingerprintSensorType) {
-        fingerprintRepository.setProperties(0, SensorStrength.STRONG, sensorType, mapOf())
-    }
-}
-
-internal const val INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP = 600
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index ca6df40..b695a0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -16,8 +16,10 @@
 
 package com.android.systemui.biometrics.ui.viewmodel
 
+import android.content.res.Configuration
 import android.hardware.biometrics.PromptInfo
 import android.hardware.face.FaceSensorPropertiesInternal
+import android.hardware.fingerprint.FingerprintSensorProperties
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
 import android.view.HapticFeedbackConstants
 import android.view.MotionEvent
@@ -36,12 +38,15 @@
 import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal
 import com.android.systemui.biometrics.shared.model.BiometricModalities
 import com.android.systemui.biometrics.shared.model.BiometricModality
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
+import com.android.systemui.biometrics.shared.model.DisplayRotation
+import com.android.systemui.biometrics.shared.model.toSensorStrength
+import com.android.systemui.biometrics.shared.model.toSensorType
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.display.data.repository.FakeDisplayRepository
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
@@ -66,6 +71,7 @@
 
 private const val USER_ID = 4
 private const val CHALLENGE = 2L
+private const val DELAY = 1000L
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -88,11 +94,22 @@
 
     private lateinit var selector: PromptSelectorInteractor
     private lateinit var viewModel: PromptViewModel
+    private lateinit var iconViewModel: PromptIconViewModel
     private val featureFlags = FakeFeatureFlags()
 
     @Before
     fun setup() {
         fingerprintRepository = FakeFingerprintPropertyRepository()
+        testCase.fingerprint?.let {
+            fingerprintRepository.setProperties(
+                it.sensorId,
+                it.sensorStrength.toSensorStrength(),
+                it.sensorType.toSensorType(),
+                it.allLocations.associateBy { sensorLocationInternal ->
+                    sensorLocationInternal.displayId
+                }
+            )
+        }
         promptRepository = FakePromptRepository()
         displayStateRepository = FakeDisplayStateRepository()
         displayRepository = FakeDisplayRepository()
@@ -110,6 +127,7 @@
 
         viewModel =
             PromptViewModel(displayStateInteractor, selector, vibrator, mContext, featureFlags)
+        iconViewModel = viewModel.iconViewModel
         featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
     }
 
@@ -123,7 +141,6 @@
             val modalities by collectLastValue(viewModel.modalities)
             val message by collectLastValue(viewModel.message)
             val size by collectLastValue(viewModel.size)
-            val legacyState by collectLastValue(viewModel.legacyState)
 
             assertThat(authenticating).isFalse()
             assertThat(authenticated?.isNotAuthenticated).isTrue()
@@ -133,7 +150,6 @@
             }
             assertThat(message).isEqualTo(PromptMessage.Empty)
             assertThat(size).isEqualTo(expectedSize)
-            assertThat(legacyState).isEqualTo(BiometricState.STATE_IDLE)
 
             val startMessage = "here we go"
             viewModel.showAuthenticating(startMessage, isRetry = false)
@@ -143,7 +159,6 @@
             assertThat(authenticated?.isNotAuthenticated).isTrue()
             assertThat(size).isEqualTo(expectedSize)
             assertButtonsVisible(negative = expectedSize != PromptSize.SMALL)
-            assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATING)
         }
 
     @Test
@@ -205,6 +220,472 @@
         assertThat(currentConstant).isEqualTo(HapticFeedbackConstants.REJECT)
     }
 
+    @Test
+    fun start_idle_and_show_authenticating_iconUpdate() =
+        runGenericTest(doNotStart = true) {
+            val currentRotation by collectLastValue(displayStateInteractor.currentRotation)
+            val iconAsset by collectLastValue(iconViewModel.iconAsset)
+            val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId)
+            val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView)
+
+            val forceExplicitFlow = testCase.isCoex && testCase.authenticatedByFingerprint
+            if (forceExplicitFlow) {
+                viewModel.ensureFingerprintHasStarted(isDelayed = true)
+            }
+
+            val startMessage = "here we go"
+            viewModel.showAuthenticating(startMessage, isRetry = false)
+
+            if (testCase.isFingerprintOnly) {
+                val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+                val shouldAnimateIconOverlay by
+                    collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+                if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+                    val expectedOverlayAsset =
+                        when (currentRotation) {
+                            DisplayRotation.ROTATION_0 ->
+                                R.raw.biometricprompt_fingerprint_to_error_landscape
+                            DisplayRotation.ROTATION_90 ->
+                                R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
+                            DisplayRotation.ROTATION_180 ->
+                                R.raw.biometricprompt_fingerprint_to_error_landscape
+                            DisplayRotation.ROTATION_270 ->
+                                R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
+                            else -> throw Exception("invalid rotation")
+                        }
+                    assertThat(iconOverlayAsset).isEqualTo(expectedOverlayAsset)
+                    assertThat(iconContentDescriptionId)
+                        .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message)
+                    assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+                } else {
+                    assertThat(iconAsset)
+                        .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie)
+                    assertThat(iconOverlayAsset).isEqualTo(-1)
+                    assertThat(iconContentDescriptionId)
+                        .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+                    assertThat(shouldAnimateIconView).isEqualTo(false)
+                    assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+                }
+            }
+
+            if (testCase.isFaceOnly) {
+                val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+                val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+                val lastPulseLightToDark by collectLastValue(iconViewModel.lastPulseLightToDark)
+
+                val expectedIconAsset =
+                    if (shouldPulseAnimation!!) {
+                        if (lastPulseLightToDark!!) {
+                            R.drawable.face_dialog_pulse_dark_to_light
+                        } else {
+                            R.drawable.face_dialog_pulse_light_to_dark
+                        }
+                    } else {
+                        R.drawable.face_dialog_pulse_dark_to_light
+                    }
+                assertThat(iconAsset).isEqualTo(expectedIconAsset)
+                assertThat(iconContentDescriptionId)
+                    .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticating)
+                assertThat(shouldAnimateIconView).isEqualTo(true)
+                assertThat(shouldRepeatAnimation).isEqualTo(true)
+            }
+
+            if (testCase.isCoex) {
+                if (testCase.confirmationRequested || forceExplicitFlow) {
+                    // explicit flow
+                    val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+                    val shouldAnimateIconOverlay by
+                        collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+                    // TODO: Update when SFPS co-ex is implemented
+                    if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+                        assertThat(iconAsset)
+                            .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie)
+                        assertThat(iconOverlayAsset).isEqualTo(-1)
+                        assertThat(iconContentDescriptionId)
+                            .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+                        assertThat(shouldAnimateIconView).isEqualTo(false)
+                        assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+                    }
+                } else {
+                    // implicit flow
+                    val shouldRepeatAnimation by
+                        collectLastValue(iconViewModel.shouldRepeatAnimation)
+                    val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+                    val lastPulseLightToDark by collectLastValue(iconViewModel.lastPulseLightToDark)
+
+                    val expectedIconAsset =
+                        if (shouldPulseAnimation!!) {
+                            if (lastPulseLightToDark!!) {
+                                R.drawable.face_dialog_pulse_dark_to_light
+                            } else {
+                                R.drawable.face_dialog_pulse_light_to_dark
+                            }
+                        } else {
+                            R.drawable.face_dialog_pulse_dark_to_light
+                        }
+                    assertThat(iconAsset).isEqualTo(expectedIconAsset)
+                    assertThat(iconContentDescriptionId)
+                        .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticating)
+                    assertThat(shouldAnimateIconView).isEqualTo(true)
+                    assertThat(shouldRepeatAnimation).isEqualTo(true)
+                }
+            }
+        }
+
+    @Test
+    fun start_authenticating_show_and_clear_error_iconUpdate() = runGenericTest {
+        val currentRotation by collectLastValue(displayStateInteractor.currentRotation)
+
+        val iconAsset by collectLastValue(iconViewModel.iconAsset)
+        val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId)
+        val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView)
+
+        val forceExplicitFlow = testCase.isCoex && testCase.authenticatedByFingerprint
+        if (forceExplicitFlow) {
+            viewModel.ensureFingerprintHasStarted(isDelayed = true)
+        }
+
+        val errorJob = launch {
+            viewModel.showTemporaryError(
+                "so sad",
+                messageAfterError = "",
+                authenticateAfterError = testCase.isFingerprintOnly || testCase.isCoex,
+            )
+            // Usually done by binder
+            iconViewModel.setPreviousIconWasError(true)
+            iconViewModel.setPreviousIconOverlayWasError(true)
+        }
+
+        if (testCase.isFingerprintOnly) {
+            val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+            val shouldAnimateIconOverlay by collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+            if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+                val expectedOverlayAsset =
+                    when (currentRotation) {
+                        DisplayRotation.ROTATION_0 ->
+                            R.raw.biometricprompt_fingerprint_to_error_landscape
+                        DisplayRotation.ROTATION_90 ->
+                            R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
+                        DisplayRotation.ROTATION_180 ->
+                            R.raw.biometricprompt_fingerprint_to_error_landscape
+                        DisplayRotation.ROTATION_270 ->
+                            R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
+                        else -> throw Exception("invalid rotation")
+                    }
+                assertThat(iconOverlayAsset).isEqualTo(expectedOverlayAsset)
+                assertThat(iconContentDescriptionId).isEqualTo(R.string.biometric_dialog_try_again)
+                assertThat(shouldAnimateIconOverlay).isEqualTo(true)
+            } else {
+                assertThat(iconAsset)
+                    .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie)
+                assertThat(iconOverlayAsset).isEqualTo(-1)
+                assertThat(iconContentDescriptionId).isEqualTo(R.string.biometric_dialog_try_again)
+                assertThat(shouldAnimateIconView).isEqualTo(true)
+                assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+            }
+
+            // Clear error, restart authenticating
+            errorJob.join()
+
+            if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+                val expectedOverlayAsset =
+                    when (currentRotation) {
+                        DisplayRotation.ROTATION_0 ->
+                            R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
+                        DisplayRotation.ROTATION_90 ->
+                            R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft
+                        DisplayRotation.ROTATION_180 ->
+                            R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
+                        DisplayRotation.ROTATION_270 ->
+                            R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright
+                        else -> throw Exception("invalid rotation")
+                    }
+                assertThat(iconOverlayAsset).isEqualTo(expectedOverlayAsset)
+                assertThat(iconContentDescriptionId)
+                    .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message)
+                assertThat(shouldAnimateIconOverlay).isEqualTo(true)
+            } else {
+                assertThat(iconAsset)
+                    .isEqualTo(R.raw.fingerprint_dialogue_error_to_fingerprint_lottie)
+                assertThat(iconOverlayAsset).isEqualTo(-1)
+                assertThat(iconContentDescriptionId)
+                    .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+                assertThat(shouldAnimateIconView).isEqualTo(true)
+                assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+            }
+        }
+
+        if (testCase.isFaceOnly) {
+            val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+            val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+
+            assertThat(shouldPulseAnimation!!).isEqualTo(false)
+            assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_dark_to_error)
+            assertThat(iconContentDescriptionId).isEqualTo(R.string.keyguard_face_failed)
+            assertThat(shouldAnimateIconView).isEqualTo(true)
+            assertThat(shouldRepeatAnimation).isEqualTo(false)
+
+            // Clear error, go to idle
+            errorJob.join()
+
+            assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_error_to_idle)
+            assertThat(iconContentDescriptionId)
+                .isEqualTo(R.string.biometric_dialog_face_icon_description_idle)
+            assertThat(shouldAnimateIconView).isEqualTo(true)
+            assertThat(shouldRepeatAnimation).isEqualTo(false)
+        }
+
+        if (testCase.isCoex) {
+            val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+            val shouldAnimateIconOverlay by collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+            // TODO: Update when SFPS co-ex is implemented
+            if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+                assertThat(iconAsset)
+                    .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie)
+                assertThat(iconOverlayAsset).isEqualTo(-1)
+                assertThat(iconContentDescriptionId).isEqualTo(R.string.biometric_dialog_try_again)
+                assertThat(shouldAnimateIconView).isEqualTo(true)
+                assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+            }
+
+            // Clear error, restart authenticating
+            errorJob.join()
+
+            if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+                assertThat(iconAsset)
+                    .isEqualTo(R.raw.fingerprint_dialogue_error_to_fingerprint_lottie)
+                assertThat(iconOverlayAsset).isEqualTo(-1)
+                assertThat(iconContentDescriptionId)
+                    .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+                assertThat(shouldAnimateIconView).isEqualTo(true)
+                assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+            }
+        }
+    }
+
+    @Test
+    fun shows_authenticated_no_errors_no_confirmation_required_iconUpdate() = runGenericTest {
+        if (!testCase.confirmationRequested) {
+            val currentRotation by collectLastValue(displayStateInteractor.currentRotation)
+
+            val iconAsset by collectLastValue(iconViewModel.iconAsset)
+            val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId)
+            val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView)
+
+            viewModel.showAuthenticated(
+                modality = testCase.authenticatedModality,
+                dismissAfterDelay = DELAY
+            )
+
+            if (testCase.isFingerprintOnly) {
+                val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+                val shouldAnimateIconOverlay by
+                    collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+                if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+                    val expectedOverlayAsset =
+                        when (currentRotation) {
+                            DisplayRotation.ROTATION_0 ->
+                                R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
+                            DisplayRotation.ROTATION_90 ->
+                                R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
+                            DisplayRotation.ROTATION_180 ->
+                                R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
+                            DisplayRotation.ROTATION_270 ->
+                                R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright
+                            else -> throw Exception("invalid rotation")
+                        }
+                    assertThat(iconOverlayAsset).isEqualTo(expectedOverlayAsset)
+                    assertThat(iconContentDescriptionId)
+                        .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message)
+                    assertThat(shouldAnimateIconOverlay).isEqualTo(true)
+                } else {
+                    val isAuthenticated by collectLastValue(viewModel.isAuthenticated)
+                    assertThat(iconAsset)
+                        .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_success_lottie)
+                    assertThat(iconOverlayAsset).isEqualTo(-1)
+                    assertThat(iconContentDescriptionId)
+                        .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+                    assertThat(shouldAnimateIconView).isEqualTo(true)
+                    assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+                }
+            }
+
+            // If co-ex, using implicit flow (explicit flow always requires confirmation)
+            if (testCase.isFaceOnly || testCase.isCoex) {
+                val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+                val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+
+                assertThat(shouldPulseAnimation!!).isEqualTo(false)
+                assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_dark_to_checkmark)
+                assertThat(iconContentDescriptionId)
+                    .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated)
+                assertThat(shouldAnimateIconView).isEqualTo(true)
+                assertThat(shouldRepeatAnimation).isEqualTo(false)
+            }
+        }
+    }
+
+    @Test
+    fun shows_pending_confirmation_iconUpdate() = runGenericTest {
+        if (
+            (testCase.isFaceOnly || testCase.isCoex) &&
+                testCase.authenticatedByFace &&
+                testCase.confirmationRequested
+        ) {
+            val iconAsset by collectLastValue(iconViewModel.iconAsset)
+            val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId)
+            val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView)
+
+            viewModel.showAuthenticated(
+                modality = testCase.authenticatedModality,
+                dismissAfterDelay = DELAY
+            )
+
+            if (testCase.isFaceOnly) {
+                val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+                val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+
+                assertThat(shouldPulseAnimation!!).isEqualTo(false)
+                assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_wink_from_dark)
+                assertThat(iconContentDescriptionId)
+                    .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated)
+                assertThat(shouldAnimateIconView).isEqualTo(true)
+                assertThat(shouldRepeatAnimation).isEqualTo(false)
+            }
+
+            // explicit flow because confirmation requested
+            if (testCase.isCoex) {
+                val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+                val shouldAnimateIconOverlay by
+                    collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+                // TODO: Update when SFPS co-ex is implemented
+                if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+                    assertThat(iconAsset)
+                        .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie)
+                    assertThat(iconOverlayAsset).isEqualTo(-1)
+                    assertThat(iconContentDescriptionId)
+                        .isEqualTo(R.string.fingerprint_dialog_authenticated_confirmation)
+                    assertThat(shouldAnimateIconView).isEqualTo(true)
+                    assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun shows_authenticated_explicitly_confirmed_iconUpdate() = runGenericTest {
+        if (
+            (testCase.isFaceOnly || testCase.isCoex) &&
+                testCase.authenticatedByFace &&
+                testCase.confirmationRequested
+        ) {
+            val iconAsset by collectLastValue(iconViewModel.iconAsset)
+            val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId)
+            val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView)
+
+            viewModel.showAuthenticated(
+                modality = testCase.authenticatedModality,
+                dismissAfterDelay = DELAY
+            )
+
+            viewModel.confirmAuthenticated()
+
+            if (testCase.isFaceOnly) {
+                val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+                val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+
+                assertThat(shouldPulseAnimation!!).isEqualTo(false)
+                assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_dark_to_checkmark)
+                assertThat(iconContentDescriptionId)
+                    .isEqualTo(R.string.biometric_dialog_face_icon_description_confirmed)
+                assertThat(shouldAnimateIconView).isEqualTo(true)
+                assertThat(shouldRepeatAnimation).isEqualTo(false)
+            }
+
+            // explicit flow because confirmation requested
+            if (testCase.isCoex) {
+                val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+                val shouldAnimateIconOverlay by
+                    collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+                // TODO: Update when SFPS co-ex is implemented
+                if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+                    assertThat(iconAsset)
+                        .isEqualTo(R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie)
+                    assertThat(iconOverlayAsset).isEqualTo(-1)
+                    assertThat(iconContentDescriptionId)
+                        .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+                    assertThat(shouldAnimateIconView).isEqualTo(true)
+                    assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun sfpsIconUpdates_onConfigurationChanged() = runGenericTest {
+        if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+            val testConfig = Configuration()
+            val folded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP - 1
+            val unfolded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP + 1
+            val currentIcon by collectLastValue(iconViewModel.iconAsset)
+
+            testConfig.smallestScreenWidthDp = folded
+            iconViewModel.onConfigurationChanged(testConfig)
+            val foldedIcon = currentIcon
+
+            testConfig.smallestScreenWidthDp = unfolded
+            iconViewModel.onConfigurationChanged(testConfig)
+            val unfoldedIcon = currentIcon
+
+            assertThat(foldedIcon).isNotEqualTo(unfoldedIcon)
+        }
+    }
+
+    @Test
+    fun sfpsIconUpdates_onRotation() = runGenericTest {
+        if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+            val currentIcon by collectLastValue(iconViewModel.iconAsset)
+
+            displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_0)
+            val iconRotation0 = currentIcon
+
+            displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90)
+            val iconRotation90 = currentIcon
+
+            displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180)
+            val iconRotation180 = currentIcon
+
+            displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270)
+            val iconRotation270 = currentIcon
+
+            assertThat(iconRotation0).isEqualTo(iconRotation180)
+            assertThat(iconRotation0).isNotEqualTo(iconRotation90)
+            assertThat(iconRotation0).isNotEqualTo(iconRotation270)
+        }
+    }
+
+    @Test
+    fun sfpsIconUpdates_onRearDisplayMode() = runGenericTest {
+        if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+            val currentIcon by collectLastValue(iconViewModel.iconAsset)
+
+            displayStateRepository.setIsInRearDisplayMode(false)
+            val iconNotRearDisplayMode = currentIcon
+
+            displayStateRepository.setIsInRearDisplayMode(true)
+            val iconRearDisplayMode = currentIcon
+
+            assertThat(iconNotRearDisplayMode).isNotEqualTo(iconRearDisplayMode)
+        }
+    }
+
     private suspend fun TestScope.showAuthenticated(
         authenticatedModality: BiometricModality,
         expectConfirmation: Boolean,
@@ -213,7 +694,6 @@
         val authenticated by collectLastValue(viewModel.isAuthenticated)
         val fpStartMode by collectLastValue(viewModel.fingerprintStartMode)
         val size by collectLastValue(viewModel.size)
-        val legacyState by collectLastValue(viewModel.legacyState)
 
         val authWithSmallPrompt =
             testCase.shouldStartAsImplicitFlow &&
@@ -221,14 +701,12 @@
         assertThat(authenticating).isTrue()
         assertThat(authenticated?.isNotAuthenticated).isTrue()
         assertThat(size).isEqualTo(if (authWithSmallPrompt) PromptSize.SMALL else PromptSize.MEDIUM)
-        assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATING)
         assertButtonsVisible(negative = !authWithSmallPrompt)
 
-        val delay = 1000L
-        viewModel.showAuthenticated(authenticatedModality, delay)
+        viewModel.showAuthenticated(authenticatedModality, DELAY)
 
         assertThat(authenticated?.isAuthenticated).isTrue()
-        assertThat(authenticated?.delay).isEqualTo(delay)
+        assertThat(authenticated?.delay).isEqualTo(DELAY)
         assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation)
         assertThat(size)
             .isEqualTo(
@@ -238,14 +716,7 @@
                     PromptSize.SMALL
                 }
             )
-        assertThat(legacyState)
-            .isEqualTo(
-                if (expectConfirmation) {
-                    BiometricState.STATE_PENDING_CONFIRMATION
-                } else {
-                    BiometricState.STATE_AUTHENTICATED
-                }
-            )
+
         assertButtonsVisible(
             cancel = expectConfirmation,
             confirm = expectConfirmation,
@@ -298,7 +769,6 @@
         val message by collectLastValue(viewModel.message)
         val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
         val size by collectLastValue(viewModel.size)
-        val legacyState by collectLastValue(viewModel.legacyState)
         val canTryAgainNow by collectLastValue(viewModel.canTryAgainNow)
 
         val errorJob = launch {
@@ -312,7 +782,6 @@
         assertThat(size).isEqualTo(PromptSize.MEDIUM)
         assertThat(message).isEqualTo(PromptMessage.Error(errorMessage))
         assertThat(messageVisible).isTrue()
-        assertThat(legacyState).isEqualTo(BiometricState.STATE_ERROR)
 
         // temporary error should disappear after a delay
         errorJob.join()
@@ -323,17 +792,6 @@
             assertThat(message).isEqualTo(PromptMessage.Empty)
             assertThat(messageVisible).isFalse()
         }
-        val clearIconError = !restart
-        assertThat(legacyState)
-            .isEqualTo(
-                if (restart) {
-                    BiometricState.STATE_AUTHENTICATING
-                } else if (clearIconError) {
-                    BiometricState.STATE_IDLE
-                } else {
-                    BiometricState.STATE_HELP
-                }
-            )
 
         assertThat(authenticating).isEqualTo(restart)
         assertThat(authenticated?.isNotAuthenticated).isTrue()
@@ -488,7 +946,6 @@
         val authenticated by collectLastValue(viewModel.isAuthenticated)
         val message by collectLastValue(viewModel.message)
         val size by collectLastValue(viewModel.size)
-        val legacyState by collectLastValue(viewModel.legacyState)
         val canTryAgain by collectLastValue(viewModel.canTryAgainNow)
 
         assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation)
@@ -506,7 +963,6 @@
 
         assertThat(authenticating).isFalse()
         assertThat(authenticated?.isAuthenticated).isTrue()
-        assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED)
         assertThat(canTryAgain).isFalse()
     }
 
@@ -524,7 +980,6 @@
         val authenticated by collectLastValue(viewModel.isAuthenticated)
         val message by collectLastValue(viewModel.message)
         val size by collectLastValue(viewModel.size)
-        val legacyState by collectLastValue(viewModel.legacyState)
         val canTryAgain by collectLastValue(viewModel.canTryAgainNow)
 
         assertThat(authenticating).isFalse()
@@ -532,8 +987,6 @@
         assertThat(authenticated?.isAuthenticated).isTrue()
 
         if (testCase.isFaceOnly && expectConfirmation) {
-            assertThat(legacyState).isEqualTo(BiometricState.STATE_PENDING_CONFIRMATION)
-
             assertThat(size).isEqualTo(PromptSize.MEDIUM)
             assertButtonsVisible(
                 cancel = true,
@@ -543,8 +996,6 @@
             viewModel.confirmAuthenticated()
             assertThat(message).isEqualTo(PromptMessage.Empty)
             assertButtonsVisible()
-        } else {
-            assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED)
         }
     }
 
@@ -563,7 +1014,6 @@
         val authenticated by collectLastValue(viewModel.isAuthenticated)
         val message by collectLastValue(viewModel.message)
         val size by collectLastValue(viewModel.size)
-        val legacyState by collectLastValue(viewModel.legacyState)
         val canTryAgain by collectLastValue(viewModel.canTryAgainNow)
 
         assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation)
@@ -581,7 +1031,6 @@
 
         assertThat(authenticating).isFalse()
         assertThat(authenticated?.isAuthenticated).isTrue()
-        assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED)
         assertThat(canTryAgain).isFalse()
     }
 
@@ -610,12 +1059,10 @@
         val message by collectLastValue(viewModel.message)
         val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
         val size by collectLastValue(viewModel.size)
-        val legacyState by collectLastValue(viewModel.legacyState)
 
         viewModel.showHelp(helpMessage)
 
         assertThat(size).isEqualTo(PromptSize.MEDIUM)
-        assertThat(legacyState).isEqualTo(BiometricState.STATE_HELP)
         assertThat(message).isEqualTo(PromptMessage.Help(helpMessage))
         assertThat(messageVisible).isTrue()
 
@@ -632,7 +1079,6 @@
         val message by collectLastValue(viewModel.message)
         val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
         val size by collectLastValue(viewModel.size)
-        val legacyState by collectLastValue(viewModel.legacyState)
         val confirmationRequired by collectLastValue(viewModel.isConfirmationRequired)
 
         if (testCase.isCoex && testCase.authenticatedByFingerprint) {
@@ -642,11 +1088,7 @@
         viewModel.showHelp(helpMessage)
 
         assertThat(size).isEqualTo(PromptSize.MEDIUM)
-        if (confirmationRequired == true) {
-            assertThat(legacyState).isEqualTo(BiometricState.STATE_PENDING_CONFIRMATION)
-        } else {
-            assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED)
-        }
+
         assertThat(message).isEqualTo(PromptMessage.Help(helpMessage))
         assertThat(messageVisible).isTrue()
         assertThat(authenticating).isFalse()
@@ -785,6 +1227,15 @@
                     authenticatedModality = BiometricModality.Fingerprint,
                 ),
                 TestCase(
+                    fingerprint =
+                        fingerprintSensorPropertiesInternal(
+                                strong = true,
+                                sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON
+                            )
+                            .first(),
+                    authenticatedModality = BiometricModality.Fingerprint,
+                ),
+                TestCase(
                     face = faceSensorPropertiesInternal(strong = true).first(),
                     authenticatedModality = BiometricModality.Face,
                     confirmationRequested = true,
@@ -794,6 +1245,16 @@
                     authenticatedModality = BiometricModality.Fingerprint,
                     confirmationRequested = true,
                 ),
+                TestCase(
+                    fingerprint =
+                        fingerprintSensorPropertiesInternal(
+                                strong = true,
+                                sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON
+                            )
+                            .first(),
+                    authenticatedModality = BiometricModality.Fingerprint,
+                    confirmationRequested = true,
+                ),
             )
 
         private val coexTestCases =
@@ -834,7 +1295,9 @@
         val modality =
             when {
                 fingerprint != null && face != null -> "coex"
-                fingerprint != null -> "fingerprint only"
+                fingerprint != null && fingerprint.isAnySidefpsType -> "fingerprint only, sideFps"
+                fingerprint != null && !fingerprint.isAnySidefpsType ->
+                    "fingerprint only, non-sideFps"
                 face != null -> "face only"
                 else -> "?"
             }
@@ -864,6 +1327,8 @@
     val isCoex: Boolean
         get() = face != null && fingerprint != null
 
+    @FingerprintSensorProperties.SensorType val sensorType: Int? = fingerprint?.sensorType
+
     val shouldStartAsImplicitFlow: Boolean
         get() = (isFaceOnly || isCoex) && !confirmationRequested
 }
@@ -890,3 +1355,5 @@
         BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face),
     )
 }
+
+internal const val INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP = 600
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
index 9373ada..f6b284f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -111,6 +111,27 @@
     }
 
     @Test
+    fun show_nullDelegate() {
+        testScope.run {
+            whenever(bouncerView.delegate).thenReturn(null)
+            mainHandler.setMode(FakeHandler.Mode.QUEUEING)
+
+            // WHEN bouncer show is requested
+            underTest.show(true)
+
+            // WHEN all queued messages are dispatched
+            mainHandler.dispatchQueuedMessages()
+
+            // THEN primary bouncer state doesn't update to show since delegate was null
+            verify(repository, never()).setPrimaryShow(true)
+            verify(repository, never()).setPrimaryShowingSoon(false)
+            verify(mPrimaryBouncerCallbackInteractor, never()).dispatchStartingToShow()
+            verify(mPrimaryBouncerCallbackInteractor, never())
+                .dispatchVisibilityChanged(View.VISIBLE)
+        }
+    }
+
+    @Test
     fun testShow_isScrimmed() {
         underTest.show(true)
         verify(repository).setKeyguardAuthenticatedBiometrics(null)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index 91409a3..fcb191b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -12,9 +12,10 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.communal.data.model.CommunalWidgetMetadata
-import com.android.systemui.communal.shared.CommunalContentSize
+import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.FakeLogBuffer
@@ -38,6 +39,7 @@
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -58,10 +60,16 @@
 
     @Mock private lateinit var userTracker: UserTracker
 
-    @Mock private lateinit var featureFlags: FeatureFlags
+    @Mock private lateinit var featureFlags: FeatureFlagsClassic
 
     @Mock private lateinit var stopwatchProviderInfo: AppWidgetProviderInfo
 
+    @Mock private lateinit var providerInfoA: AppWidgetProviderInfo
+
+    @Mock private lateinit var providerInfoB: AppWidgetProviderInfo
+
+    @Mock private lateinit var providerInfoC: AppWidgetProviderInfo
+
     private lateinit var communalRepository: FakeCommunalRepository
 
     private lateinit var logBuffer: LogBuffer
@@ -70,29 +78,34 @@
 
     private val testScope = TestScope(testDispatcher)
 
+    private val fakeAllowlist =
+        listOf(
+            "com.android.fake/WidgetProviderA",
+            "com.android.fake/WidgetProviderB",
+            "com.android.fake/WidgetProviderC",
+        )
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
         logBuffer = FakeLogBuffer.Factory.create()
-
-        featureFlagEnabled(true)
         communalRepository = FakeCommunalRepository()
-        communalRepository.setIsCommunalEnabled(true)
 
-        overrideResource(
-            R.array.config_communalWidgetAllowlist,
-            arrayOf(componentName1, componentName2)
-        )
+        communalEnabled(true)
+        widgetOnKeyguardEnabled(true)
+        setAppWidgetIds(emptyList())
+
+        overrideResource(R.array.config_communalWidgetAllowlist, fakeAllowlist.toTypedArray())
 
         whenever(stopwatchProviderInfo.loadLabel(any())).thenReturn("Stopwatch")
         whenever(userTracker.userHandle).thenReturn(userHandle)
     }
 
     @Test
-    fun broadcastReceiver_featureDisabled_doNotRegisterUserUnlockedBroadcastReceiver() =
+    fun broadcastReceiver_communalDisabled_doNotRegisterUserUnlockedBroadcastReceiver() =
         testScope.runTest {
-            featureFlagEnabled(false)
+            communalEnabled(false)
             val repository = initCommunalWidgetRepository()
             collectLastValue(repository.stopwatchAppWidgetInfo)()
             verifyBroadcastReceiverNeverRegistered()
@@ -129,7 +142,7 @@
             job.cancel()
             runCurrent()
 
-            Mockito.verify(broadcastDispatcher).unregisterReceiver(receiver)
+            verify(broadcastDispatcher).unregisterReceiver(receiver)
         }
 
     @Test
@@ -166,7 +179,7 @@
             installedProviders(listOf(stopwatchProviderInfo))
             val repository = initCommunalWidgetRepository()
             collectLastValue(repository.stopwatchAppWidgetInfo)()
-            Mockito.verify(appWidgetHost).allocateAppWidgetId()
+            verify(appWidgetHost).allocateAppWidgetId()
         }
 
     @Test
@@ -185,8 +198,8 @@
 
             // Verify app widget id allocated
             assertThat(lastStopwatchProviderInfo()?.appWidgetId).isEqualTo(123456)
-            Mockito.verify(appWidgetHost).allocateAppWidgetId()
-            Mockito.verify(appWidgetHost, Mockito.never()).deleteAppWidgetId(anyInt())
+            verify(appWidgetHost).allocateAppWidgetId()
+            verify(appWidgetHost, Mockito.never()).deleteAppWidgetId(anyInt())
 
             // User locked again
             userUnlocked(false)
@@ -194,7 +207,7 @@
 
             // Verify app widget id deleted
             assertThat(lastStopwatchProviderInfo()).isNull()
-            Mockito.verify(appWidgetHost).deleteAppWidgetId(123456)
+            verify(appWidgetHost).deleteAppWidgetId(123456)
         }
 
     @Test
@@ -203,13 +216,13 @@
             userUnlocked(false)
             val repository = initCommunalWidgetRepository()
             collectLastValue(repository.stopwatchAppWidgetInfo)()
-            Mockito.verify(appWidgetHost, Mockito.never()).startListening()
+            verify(appWidgetHost, Mockito.never()).startListening()
 
             userUnlocked(true)
             broadcastReceiverUpdate()
             collectLastValue(repository.stopwatchAppWidgetInfo)()
 
-            Mockito.verify(appWidgetHost).startListening()
+            verify(appWidgetHost).startListening()
         }
 
     @Test
@@ -223,14 +236,14 @@
             broadcastReceiverUpdate()
             collectLastValue(repository.stopwatchAppWidgetInfo)()
 
-            Mockito.verify(appWidgetHost).startListening()
-            Mockito.verify(appWidgetHost, Mockito.never()).stopListening()
+            verify(appWidgetHost).startListening()
+            verify(appWidgetHost, Mockito.never()).stopListening()
 
             userUnlocked(false)
             broadcastReceiverUpdate()
             collectLastValue(repository.stopwatchAppWidgetInfo)()
 
-            Mockito.verify(appWidgetHost).stopListening()
+            verify(appWidgetHost).stopListening()
         }
 
     @Test
@@ -241,21 +254,80 @@
             assertThat(
                     listOf(
                         CommunalWidgetMetadata(
-                            componentName = componentName1,
-                            priority = 2,
-                            sizes = listOf(CommunalContentSize.HALF)
+                            componentName = fakeAllowlist[0],
+                            priority = 3,
+                            sizes = listOf(CommunalContentSize.HALF),
                         ),
                         CommunalWidgetMetadata(
-                            componentName = componentName2,
+                            componentName = fakeAllowlist[1],
+                            priority = 2,
+                            sizes = listOf(CommunalContentSize.HALF),
+                        ),
+                        CommunalWidgetMetadata(
+                            componentName = fakeAllowlist[2],
                             priority = 1,
-                            sizes = listOf(CommunalContentSize.HALF)
-                        )
+                            sizes = listOf(CommunalContentSize.HALF),
+                        ),
                     )
                 )
                 .containsExactly(*communalWidgetAllowlist.toTypedArray())
         }
     }
 
+    // This behavior is temporary before the local database is set up.
+    @Test
+    fun communalWidgets_withPreviouslyBoundWidgets_removeEachBinding() =
+        testScope.runTest {
+            whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(1, 2, 3)
+            setAppWidgetIds(listOf(1, 2, 3))
+            whenever(appWidgetManager.getAppWidgetInfo(anyInt())).thenReturn(providerInfoA)
+            userUnlocked(true)
+
+            val repository = initCommunalWidgetRepository()
+
+            collectLastValue(repository.communalWidgets)()
+
+            verify(appWidgetHost).deleteAppWidgetId(1)
+            verify(appWidgetHost).deleteAppWidgetId(2)
+            verify(appWidgetHost).deleteAppWidgetId(3)
+        }
+
+    @Test
+    fun communalWidgets_allowlistNotEmpty_bindEachWidgetFromTheAllowlist() =
+        testScope.runTest {
+            whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(0, 1, 2)
+            userUnlocked(true)
+
+            whenever(appWidgetManager.getAppWidgetInfo(0)).thenReturn(providerInfoA)
+            whenever(appWidgetManager.getAppWidgetInfo(1)).thenReturn(providerInfoB)
+            whenever(appWidgetManager.getAppWidgetInfo(2)).thenReturn(providerInfoC)
+
+            val repository = initCommunalWidgetRepository()
+
+            val inventory by collectLastValue(repository.communalWidgets)
+
+            assertThat(
+                    listOf(
+                        CommunalWidgetContentModel(
+                            appWidgetId = 0,
+                            providerInfo = providerInfoA,
+                            priority = 3,
+                        ),
+                        CommunalWidgetContentModel(
+                            appWidgetId = 1,
+                            providerInfo = providerInfoB,
+                            priority = 2,
+                        ),
+                        CommunalWidgetContentModel(
+                            appWidgetId = 2,
+                            providerInfo = providerInfoC,
+                            priority = 1,
+                        ),
+                    )
+                )
+                .containsExactly(*inventory!!.toTypedArray())
+        }
+
     private fun initCommunalWidgetRepository(): CommunalWidgetRepositoryImpl {
         return CommunalWidgetRepositoryImpl(
             context,
@@ -272,7 +344,7 @@
     }
 
     private fun verifyBroadcastReceiverRegistered() {
-        Mockito.verify(broadcastDispatcher)
+        verify(broadcastDispatcher)
             .registerReceiver(
                 any(),
                 any(),
@@ -284,7 +356,7 @@
     }
 
     private fun verifyBroadcastReceiverNeverRegistered() {
-        Mockito.verify(broadcastDispatcher, Mockito.never())
+        verify(broadcastDispatcher, Mockito.never())
             .registerReceiver(
                 any(),
                 any(),
@@ -297,7 +369,7 @@
 
     private fun broadcastReceiverUpdate(): BroadcastReceiver {
         val broadcastReceiverCaptor = kotlinArgumentCaptor<BroadcastReceiver>()
-        Mockito.verify(broadcastDispatcher)
+        verify(broadcastDispatcher)
             .registerReceiver(
                 broadcastReceiverCaptor.capture(),
                 any(),
@@ -310,7 +382,11 @@
         return broadcastReceiverCaptor.value
     }
 
-    private fun featureFlagEnabled(enabled: Boolean) {
+    private fun communalEnabled(enabled: Boolean) {
+        communalRepository.setIsCommunalEnabled(enabled)
+    }
+
+    private fun widgetOnKeyguardEnabled(enabled: Boolean) {
         whenever(featureFlags.isEnabled(Flags.WIDGET_ON_KEYGUARD)).thenReturn(enabled)
     }
 
@@ -322,8 +398,7 @@
         whenever(appWidgetManager.installedProviders).thenReturn(providers)
     }
 
-    companion object {
-        const val componentName1 = "component name 1"
-        const val componentName2 = "component name 2"
+    private fun setAppWidgetIds(ids: List<Int>) {
+        whenever(appWidgetHost.appWidgetIds).thenReturn(ids.toIntArray())
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index cdc42e0..8e21f29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -22,7 +22,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.data.repository.FakeCommunalRepository
 import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
 import com.android.systemui.coroutines.collectLastValue
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index 5afc405..ad80a06 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.keyguard.util.KeyguardTransitionRunner
@@ -86,7 +87,7 @@
         }
 
     @Test
-    fun startingSecondTransitionWillCancelTheFirstTransition() =
+    fun startingSecondTransitionWillCancelTheFirstTransitionAndUseLastValue() =
         TestScope().runTest {
             val steps = mutableListOf<TransitionStep>()
             val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
@@ -100,12 +101,19 @@
             val job2 = underTest.transition(LOCKSCREEN, AOD).onEach { steps.add(it) }.launchIn(this)
             runner.startTransition(
                 this,
-                TransitionInfo(OWNER_NAME, LOCKSCREEN, AOD, getAnimator()),
+                TransitionInfo(
+                    OWNER_NAME,
+                    LOCKSCREEN,
+                    AOD,
+                    getAnimator(),
+                    TransitionModeOnCanceled.LAST_VALUE
+                ),
             )
 
             val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.1))
             assertSteps(steps.subList(0, 4), firstTransitionSteps, AOD, LOCKSCREEN)
 
+            // Second transition starts from .1 (LAST_VALUE)
             val secondTransitionSteps = listWithStep(step = BigDecimal(.1), start = BigDecimal(.1))
             assertSteps(steps.subList(4, steps.size), secondTransitionSteps, LOCKSCREEN, AOD)
 
@@ -114,6 +122,76 @@
         }
 
     @Test
+    fun startingSecondTransitionWillCancelTheFirstTransitionAndUseReset() =
+        TestScope().runTest {
+            val steps = mutableListOf<TransitionStep>()
+            val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
+            runner.startTransition(
+                this,
+                TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, getAnimator()),
+                maxFrames = 3,
+            )
+
+            // Now start 2nd transition, which will interrupt the first
+            val job2 = underTest.transition(LOCKSCREEN, AOD).onEach { steps.add(it) }.launchIn(this)
+            runner.startTransition(
+                this,
+                TransitionInfo(
+                    OWNER_NAME,
+                    LOCKSCREEN,
+                    AOD,
+                    getAnimator(),
+                    TransitionModeOnCanceled.RESET
+                ),
+            )
+
+            val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.1))
+            assertSteps(steps.subList(0, 4), firstTransitionSteps, AOD, LOCKSCREEN)
+
+            // Second transition starts from 0 (RESET)
+            val secondTransitionSteps = listWithStep(start = BigDecimal(0), step = BigDecimal(.1))
+            assertSteps(steps.subList(4, steps.size), secondTransitionSteps, LOCKSCREEN, AOD)
+
+            job.cancel()
+            job2.cancel()
+        }
+
+    @Test
+    fun startingSecondTransitionWillCancelTheFirstTransitionAndUseReverse() =
+        TestScope().runTest {
+            val steps = mutableListOf<TransitionStep>()
+            val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
+            runner.startTransition(
+                this,
+                TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, getAnimator()),
+                maxFrames = 3,
+            )
+
+            // Now start 2nd transition, which will interrupt the first
+            val job2 = underTest.transition(LOCKSCREEN, AOD).onEach { steps.add(it) }.launchIn(this)
+            runner.startTransition(
+                this,
+                TransitionInfo(
+                    OWNER_NAME,
+                    LOCKSCREEN,
+                    AOD,
+                    getAnimator(),
+                    TransitionModeOnCanceled.REVERSE
+                ),
+            )
+
+            val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.1))
+            assertSteps(steps.subList(0, 4), firstTransitionSteps, AOD, LOCKSCREEN)
+
+            // Second transition starts from .9 (REVERSE)
+            val secondTransitionSteps = listWithStep(start = BigDecimal(0.9), step = BigDecimal(.1))
+            assertSteps(steps.subList(4, steps.size), secondTransitionSteps, LOCKSCREEN, AOD)
+
+            job.cancel()
+            job2.cancel()
+        }
+
+    @Test
     fun nullAnimatorEnablesManualControlWithUpdateTransition() =
         TestScope().runTest {
             val steps = mutableListOf<TransitionStep>()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index f3930a3..275ac80 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -57,7 +57,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
-import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
 import org.mockito.Mockito.clearInvocations
@@ -244,7 +243,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to PRIMARY_BOUNCER should occur
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -271,7 +270,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -298,7 +297,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -328,7 +327,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DREAMING should occur
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -359,7 +358,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DREAMING_LOCKSCREEN_HOSTED should occur
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -386,7 +385,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -413,7 +412,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -446,7 +445,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to Lockscreen should occur
             assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
@@ -474,7 +473,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to Gone should occur
             assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
@@ -504,7 +503,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to PRIMARY_BOUNCER should occur
             assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
@@ -537,7 +536,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
@@ -569,7 +568,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to OCCLUDED should occur
             assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
@@ -593,7 +592,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
@@ -625,7 +624,7 @@
             advanceUntilIdle()
 
             // THEN the transition is ignored
-            verify(transitionRepository, never()).startTransition(any(), anyBoolean())
+            verify(transitionRepository, never()).startTransition(any())
 
             coroutineContext.cancelChildren()
         }
@@ -642,7 +641,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
@@ -669,7 +668,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -696,7 +695,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to AOD should occur
             assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -719,7 +718,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to AOD should occur
             assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -749,7 +748,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DREAMING should occur
             assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -780,7 +779,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DREAMING_LOCKSCREEN_HOSTED should occur
             assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -806,7 +805,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to PRIMARY_BOUNCER should occur
             assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -838,7 +837,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to AOD should occur
             assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -871,7 +870,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -901,7 +900,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to LOCKSCREEN should occur
             assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -929,7 +928,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to AOD should occur
             assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -957,7 +956,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -981,7 +980,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to LOCKSCREEN should occur
             assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -1011,7 +1010,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition back to DREAMING_LOCKSCREEN_HOSTED should occur
             assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -1042,7 +1041,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to GONE should occur
             assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -1071,7 +1070,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to LOCKSCREEN should occur
             assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -1096,7 +1095,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to AlternateBouncer should occur
             assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -1121,7 +1120,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to AlternateBouncer should occur
             assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -1147,7 +1146,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to OCCLUDED should occur
             assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -1172,7 +1171,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to OCCLUDED should occur
             assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
@@ -1199,7 +1198,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to OCCLUDED should occur
             assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
@@ -1223,7 +1222,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to OCCLUDED should occur
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -1253,7 +1252,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             // THEN a transition to OCCLUDED should occur
             assertThat(info.ownerName).isEqualTo("FromAodTransitionInteractor")
@@ -1287,7 +1286,7 @@
             // THEN a transition from LOCKSCREEN => OCCLUDED should occur
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
             assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
@@ -1318,7 +1317,7 @@
             // THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
             assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
@@ -1339,7 +1338,7 @@
             // THEN a transition from PRIMARY_BOUNCER => LOCKSCREEN should occur
             val info2 =
                 withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture())
                 }
             assertThat(info2.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
             assertThat(info2.to).isEqualTo(KeyguardState.LOCKSCREEN)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
index 54fc493..1abb441 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
@@ -42,7 +42,7 @@
     private var frameCount = 1L
     private var frames = MutableStateFlow(Pair<Long, FrameCallback?>(0L, null))
     private var job: Job? = null
-    private var isTerminated = false
+    @Volatile private var isTerminated = false
 
     /**
      * For transitions being directed by an animator. Will control the number of frames being
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
index 0552ced..0e4b113 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
@@ -124,11 +124,44 @@
     }
 
     @Test
-    fun testLongClickIntent() {
+    fun testLongClickIntent_safetyCenterEnabled() {
         whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(true)
-        assertThat(tile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS)
+        val cameraTile = CameraToggleTile(
+                host,
+                uiEventLogger,
+                testableLooper.looper,
+                Handler(testableLooper.looper),
+                metricsLogger,
+                FalsingManagerFake(),
+                statusBarStateController,
+                activityStarter,
+                qsLogger,
+                privacyController,
+                keyguardStateController,
+                safetyCenterManager)
+        assertThat(cameraTile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS)
+        cameraTile.destroy()
+        testableLooper.processAllMessages()
+    }
 
+    @Test
+    fun testLongClickIntent_safetyCenterDisabled() {
         whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(false)
+        val cameraTile = CameraToggleTile(
+                host,
+                uiEventLogger,
+                testableLooper.looper,
+                Handler(testableLooper.looper),
+                metricsLogger,
+                FalsingManagerFake(),
+                statusBarStateController,
+                activityStarter,
+                qsLogger,
+                privacyController,
+                keyguardStateController,
+                safetyCenterManager)
         assertThat(tile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_SETTINGS)
+        cameraTile.destroy()
+        testableLooper.processAllMessages()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
index 0fcfdb6..b98a7570 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
@@ -123,11 +123,44 @@
     }
 
     @Test
-    fun testLongClickIntent() {
+    fun testLongClickIntent_safetyCenterEnabled() {
         whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(true)
-        assertThat(tile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS)
+        val micTile = MicrophoneToggleTile(
+                host,
+                uiEventLogger,
+                testableLooper.looper,
+                Handler(testableLooper.looper),
+                metricsLogger,
+                FalsingManagerFake(),
+                statusBarStateController,
+                activityStarter,
+                qsLogger,
+                privacyController,
+                keyguardStateController,
+                safetyCenterManager)
+        assertThat(micTile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS)
+        micTile.destroy()
+        testableLooper.processAllMessages()
+    }
 
+    @Test
+    fun testLongClickIntent_safetyCenterDisabled() {
         whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(false)
-        assertThat(tile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_SETTINGS)
+        val micTile = MicrophoneToggleTile(
+                host,
+                uiEventLogger,
+                testableLooper.looper,
+                Handler(testableLooper.looper),
+                metricsLogger,
+                FalsingManagerFake(),
+                statusBarStateController,
+                activityStarter,
+                qsLogger,
+                privacyController,
+                keyguardStateController,
+                safetyCenterManager)
+        assertThat(micTile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_SETTINGS)
+        micTile.destroy()
+        testableLooper.processAllMessages()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
index 5b3d45b..ac03073 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
@@ -22,11 +22,13 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.Dialog;
 import android.os.Handler;
 import android.service.quicksettings.Tile;
 import android.testing.AndroidTestingRunner;
@@ -35,11 +37,11 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -48,6 +50,7 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
 import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -89,6 +92,10 @@
     private PanelInteractor mPanelInteractor;
     @Mock
     private QsEventLogger mUiEventLogger;
+    @Mock
+    private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
+    @Mock
+    private Dialog mPermissionDialogPrompt;
 
     private TestableLooper mTestableLooper;
     private ScreenRecordTile mTile;
@@ -116,7 +123,8 @@
                 mKeyguardDismissUtil,
                 mKeyguardStateController,
                 mDialogLaunchAnimator,
-                mPanelInteractor
+                mPanelInteractor,
+                mMediaProjectionMetricsLogger
         );
 
         mTile.initialize();
@@ -280,4 +288,27 @@
         assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.qs_screen_record_icon_off));
     }
 
+    @Test
+    public void showingDialogPrompt_logsMediaProjectionPermissionRequested() {
+        when(mController.isStarting()).thenReturn(false);
+        when(mController.isRecording()).thenReturn(false);
+        when(mController.createScreenRecordDialog(any(), any(), any(), any(), any()))
+                .thenReturn(mPermissionDialogPrompt);
+
+        mTile.handleClick(null /* view */);
+        mTestableLooper.processAllMessages();
+
+        verify(mController).createScreenRecordDialog(any(), eq(mFeatureFlags),
+                eq(mDialogLaunchAnimator), eq(mActivityStarter), any());
+        var onDismissAction = ArgumentCaptor.forClass(ActivityStarter.OnDismissAction.class);
+        verify(mKeyguardDismissUtil).executeWhenUnlocked(
+                onDismissAction.capture(), anyBoolean(), anyBoolean());
+        assertNotNull(onDismissAction.getValue());
+
+        onDismissAction.getValue().onDismiss();
+
+        verify(mPermissionDialogPrompt).show();
+        verify(mMediaProjectionMetricsLogger).notifyPermissionRequestDisplayed();
+    }
+
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
index f1fcee3..9907278 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
@@ -23,11 +23,14 @@
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dump.LogcatEchoTrackerAlways
 import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import java.io.PrintWriter
 import java.io.StringWriter
@@ -42,6 +45,7 @@
 class QSTileLoggerTest : SysuiTestCase() {
 
     @Mock private lateinit var statusBarController: StatusBarStateController
+    @Mock private lateinit var logBufferFactory: LogBufferFactory
 
     private val chattyLogBuffer = LogBuffer("TestChatty", 5, LogcatEchoTrackerAlways())
     private val logBuffer = LogBuffer("Test", 1, LogcatEchoTrackerAlways())
@@ -51,10 +55,11 @@
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
+        whenever(logBufferFactory.create(any(), any(), any())).thenReturn(logBuffer)
         underTest =
             QSTileLogger(
                 mapOf(TileSpec.create("chatty_tile") to chattyLogBuffer),
-                { logBuffer },
+                logBufferFactory,
                 statusBarController
             )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 4f3216d..da49230 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -21,7 +21,9 @@
 import android.testing.TestableLooper.RunWithLooper
 import android.view.KeyEvent
 import android.view.MotionEvent
+import android.view.View
 import android.view.ViewGroup
+import androidx.core.view.contains
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardMessageAreaController
 import com.android.keyguard.KeyguardSecurityContainerController
@@ -29,6 +31,8 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.LockIconViewController
 import com.android.keyguard.dagger.KeyguardBouncerComponent
+import com.android.systemui.FakeFeatureFlagsImpl
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.back.domain.interactor.BackActionInteractor
 import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
@@ -43,11 +47,19 @@
 import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.classifier.FalsingCollectorFake
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.compose.ComposeFacade.isComposeAvailable
 import com.android.systemui.dock.DockManager
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.dump.logcatLogBuffer
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags.ALTERNATE_BOUNCER_VIEW
+import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
+import com.android.systemui.flags.Flags.MIGRATE_NSSL
+import com.android.systemui.flags.Flags.REVAMPED_BOUNCER_MESSAGES
+import com.android.systemui.flags.Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION
+import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON
+import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_FEATURES
 import com.android.systemui.flags.SystemPropertiesHelper
 import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler
 import com.android.systemui.keyguard.DismissCallbackRegistry
@@ -83,6 +95,7 @@
 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 +110,7 @@
 import org.mockito.Mockito.anyFloat
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
+import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 import java.util.Optional
@@ -135,6 +149,7 @@
     @Mock
     private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener
     @Mock private lateinit var notificationInsetsController: NotificationInsetsController
+    @Mock private lateinit var mCommunalViewModel: CommunalViewModel
     @Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
     @Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
     @Mock lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController
@@ -159,7 +174,8 @@
 
     private lateinit var testScope: TestScope
 
-    private lateinit var featureFlags: FakeFeatureFlags
+    private lateinit var featureFlagsClassic: FakeFeatureFlagsClassic
+    private lateinit var featureFlags: FakeFeatureFlagsImpl
 
     @Before
     fun setUp() {
@@ -174,14 +190,16 @@
         whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition)
             .thenReturn(emptyFlow<TransitionStep>())
 
-        featureFlags = FakeFeatureFlags()
-        featureFlags.set(Flags.TRACKPAD_GESTURE_COMMON, true)
-        featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false)
-        featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
-        featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
-        featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
-        featureFlags.set(Flags.MIGRATE_NSSL, false)
-        featureFlags.set(Flags.ALTERNATE_BOUNCER_VIEW, false)
+        featureFlagsClassic = FakeFeatureFlagsClassic()
+        featureFlagsClassic.set(TRACKPAD_GESTURE_COMMON, true)
+        featureFlagsClassic.set(TRACKPAD_GESTURE_FEATURES, false)
+        featureFlagsClassic.set(SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
+        featureFlagsClassic.set(REVAMPED_BOUNCER_MESSAGES, true)
+        featureFlagsClassic.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
+        featureFlagsClassic.set(MIGRATE_NSSL, false)
+        featureFlagsClassic.set(ALTERNATE_BOUNCER_VIEW, false)
+
+        featureFlags = FakeFeatureFlagsImpl()
 
         testScope = TestScope()
         fakeClock = FakeSystemClock()
@@ -216,14 +234,16 @@
                 mock(KeyguardMessageAreaController.Factory::class.java),
                 keyguardTransitionInteractor,
                 primaryBouncerToGoneTransitionViewModel,
+                mCommunalViewModel,
                 notificationExpansionRepository,
+                featureFlagsClassic,
                 featureFlags,
                 fakeClock,
                 BouncerMessageInteractor(
                     repository = BouncerMessageRepositoryImpl(),
                     userRepository = FakeUserRepository(),
                     countDownTimerUtil = mock(CountDownTimerUtil::class.java),
-                    featureFlags = featureFlags,
+                    featureFlags = featureFlagsClassic,
                     updateMonitor = mock(KeyguardUpdateMonitor::class.java),
                     biometricSettingsRepository = FakeBiometricSettingsRepository(),
                     applicationScope = testScope.backgroundScope,
@@ -443,7 +463,7 @@
         whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT))
                 .thenReturn(true)
 
-        featureFlags.set(Flags.MIGRATE_NSSL, true)
+        featureFlagsClassic.set(MIGRATE_NSSL, true)
 
         // THEN touch should NOT be intercepted by NotificationShade
         assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isFalse()
@@ -460,7 +480,7 @@
         whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT))
                 .thenReturn(false)
 
-        featureFlags.set(Flags.MIGRATE_NSSL, true)
+        featureFlagsClassic.set(MIGRATE_NSSL, true)
 
         // THEN touch should NOT be intercepted by NotificationShade
         assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue()
@@ -474,6 +494,48 @@
         }
 
     @Test
+    fun setsUpCommunalHubLayout_whenFlagEnabled() {
+        if (!isComposeAvailable()) {
+            return
+        }
+
+        featureFlags.setFlag(FLAG_COMMUNAL_HUB, true)
+
+        val mockCommunalPlaceholder = mock(View::class.java)
+        val fakeViewIndex = 20
+        whenever(view.findViewById<View>(R.id.communal_ui_stub)).thenReturn(mockCommunalPlaceholder)
+        whenever(view.indexOfChild(mockCommunalPlaceholder)).thenReturn(fakeViewIndex)
+        whenever(view.context).thenReturn(context)
+
+        underTest.setupCommunalHubLayout()
+
+        // Communal view added as a child of the container at the proper index, the stub is removed.
+        verify(view).removeView(mockCommunalPlaceholder)
+        verify(view).addView(any(), eq(fakeViewIndex))
+    }
+
+    @Test
+    fun doesNotSetupCommunalHubLayout_whenFlagDisabled() {
+        if (!isComposeAvailable()) {
+            return
+        }
+
+        featureFlags.setFlag(FLAG_COMMUNAL_HUB, false)
+
+        val mockCommunalPlaceholder = mock(View::class.java)
+        val fakeViewIndex = 20
+        whenever(view.findViewById<View>(R.id.communal_ui_stub)).thenReturn(mockCommunalPlaceholder)
+        whenever(view.indexOfChild(mockCommunalPlaceholder)).thenReturn(fakeViewIndex)
+        whenever(view.context).thenReturn(context)
+
+        underTest.setupCommunalHubLayout()
+
+        // No adding or removing of views occurs.
+        verify(view, times(0)).removeView(mockCommunalPlaceholder)
+        verify(view, times(0)).addView(any(), eq(fakeViewIndex))
+    }
+
+    @Test
     fun forwardsDispatchKeyEvent() {
         val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_B)
         interactionEventHandler.dispatchKeyEvent(keyEvent)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 4d3eab4..c94741f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -28,6 +28,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.LockIconViewController
 import com.android.keyguard.dagger.KeyguardBouncerComponent
+import com.android.systemui.FakeFeatureFlagsImpl
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.back.domain.interactor.BackActionInteractor
 import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
@@ -42,6 +43,7 @@
 import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.classifier.FalsingCollectorFake
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.dock.DockManager
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.dump.logcatLogBuffer
@@ -142,6 +144,7 @@
     private lateinit var unfoldTransitionProgressProvider:
         Optional<UnfoldTransitionProgressProvider>
     @Mock private lateinit var notificationInsetsController: NotificationInsetsController
+    @Mock private lateinit var mCommunalViewModel: CommunalViewModel
     @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     @Mock lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
     @Mock lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
@@ -218,8 +221,10 @@
                 Mockito.mock(KeyguardMessageAreaController.Factory::class.java),
                 keyguardTransitionInteractor,
                 primaryBouncerToGoneTransitionViewModel,
+                mCommunalViewModel,
                 NotificationExpansionRepository(),
                 featureFlags,
+                FakeFeatureFlagsImpl(),
                 FakeSystemClock(),
                 BouncerMessageInteractor(
                     repository = BouncerMessageRepositoryImpl(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
index 81382a4..3a260ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
@@ -445,105 +445,6 @@
         }
 
     @Test
-    fun expanding_shadeDraggedDown_expandingTrue() =
-        testScope.runTest() {
-            val actual by collectLastValue(underTest.isAnyExpanding)
-
-            // GIVEN shade and QS collapsed
-            shadeRepository.setLegacyShadeExpansion(0f)
-            shadeRepository.setQsExpansion(0f)
-            runCurrent()
-
-            // WHEN shade partially expanded
-            shadeRepository.setLegacyShadeExpansion(.5f)
-            runCurrent()
-
-            // THEN anyExpanding is true
-            assertThat(actual).isTrue()
-        }
-
-    @Test
-    fun expanding_qsDraggedDown_expandingTrue() =
-        testScope.runTest() {
-            val actual by collectLastValue(underTest.isAnyExpanding)
-
-            // GIVEN shade and QS collapsed
-            shadeRepository.setLegacyShadeExpansion(0f)
-            shadeRepository.setQsExpansion(0f)
-            runCurrent()
-
-            // WHEN shade partially expanded
-            shadeRepository.setQsExpansion(.5f)
-            runCurrent()
-
-            // THEN anyExpanding is true
-            assertThat(actual).isTrue()
-        }
-
-    @Test
-    fun expanding_shadeDraggedUpAndDown() =
-        testScope.runTest() {
-            val actual by collectLastValue(underTest.isAnyExpanding)
-
-            // WHEN shade starts collapsed then partially expanded
-            shadeRepository.setLegacyShadeExpansion(0f)
-            shadeRepository.setLegacyShadeExpansion(.5f)
-            shadeRepository.setQsExpansion(0f)
-            runCurrent()
-
-            // THEN anyExpanding is true
-            assertThat(actual).isTrue()
-
-            // WHEN shade dragged up a bit
-            shadeRepository.setLegacyShadeExpansion(.2f)
-            runCurrent()
-
-            // THEN anyExpanding is still true
-            assertThat(actual).isTrue()
-
-            // WHEN shade dragged down a bit
-            shadeRepository.setLegacyShadeExpansion(.7f)
-            runCurrent()
-
-            // THEN anyExpanding is still true
-            assertThat(actual).isTrue()
-
-            // WHEN shade fully expanded
-            shadeRepository.setLegacyShadeExpansion(1f)
-            runCurrent()
-
-            // THEN anyExpanding is now false
-            assertThat(actual).isFalse()
-
-            // WHEN shade dragged up a bit
-            shadeRepository.setLegacyShadeExpansion(.7f)
-            runCurrent()
-
-            // THEN anyExpanding is still false
-            assertThat(actual).isFalse()
-        }
-
-    @Test
-    fun expanding_shadeDraggedDownThenUp_expandingFalse() =
-        testScope.runTest() {
-            val actual by collectLastValue(underTest.isAnyExpanding)
-
-            // GIVEN shade starts collapsed
-            shadeRepository.setLegacyShadeExpansion(0f)
-            shadeRepository.setQsExpansion(0f)
-            runCurrent()
-
-            // WHEN shade expands but doesn't complete
-            shadeRepository.setLegacyShadeExpansion(.5f)
-            runCurrent()
-            shadeRepository.setLegacyShadeExpansion(0f)
-            runCurrent()
-
-            // THEN anyExpanding is false
-            assertThat(actual).isFalse()
-        }
-
-    @Test
     fun lockscreenShadeExpansion_idle_onScene() =
         testScope.runTest() {
             // GIVEN an expansion flow based on transitions to and from a scene
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
index c664c39..2ef4374 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
@@ -18,10 +18,16 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 
+import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -33,8 +39,8 @@
 import android.view.animation.Interpolator;
 
 import com.android.app.animation.Interpolators;
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.stack.AnimationFilter;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -85,7 +91,7 @@
             return mEffectiveProperty;
         }
     };
-    private AnimatorListenerAdapter mFinishListener = mock(AnimatorListenerAdapter.class);
+    private AnimatorListenerAdapter mFinishListener;
     private AnimationProperties mAnimationProperties = new AnimationProperties() {
         @Override
         public AnimationFilter getAnimationFilter() {
@@ -104,6 +110,7 @@
     @Before
     public void setUp() {
         mView = new View(getContext());
+        mFinishListener = mock(AnimatorListenerAdapter.class);
     }
 
     @Test
@@ -229,6 +236,32 @@
     }
 
     @Test
+    public void testListenerCallbackOrderAndTagState() {
+        mAnimationFilter.reset();
+        mAnimationFilter.animate(mProperty.getProperty());
+        mAnimationProperties.setCustomInterpolator(mEffectiveProperty, mTestInterpolator);
+        mAnimationProperties.setDuration(500);
+
+        // Validates that the onAnimationEnd function set by PropertyAnimator was run first.
+        doAnswer(invocation -> {
+            assertNull(mView.getTag(mProperty.getAnimatorTag()));
+            return null;
+        })
+                .when(mFinishListener)
+                .onAnimationEnd(any(Animator.class), anyBoolean());
+
+        // Begin the animation and verify it set state correctly
+        PropertyAnimator.startAnimation(mView, mProperty, 200f, mAnimationProperties);
+        ValueAnimator animator = ViewState.getChildTag(mView, mProperty.getAnimatorTag());
+        assertNotNull(animator);
+        assertNotNull(mView.getTag(mProperty.getAnimatorTag()));
+
+        // Terminate the animation to run end runners, and validate they executed.
+        animator.end();
+        verify(mFinishListener).onAnimationEnd(animator, false);
+    }
+
+    @Test
     public void testIsAnimating() {
         mAnimationFilter.reset();
         mAnimationFilter.animate(mProperty.getProperty());
@@ -236,4 +269,4 @@
         PropertyAnimator.startAnimation(mView, mProperty, 200f, mAnimationProperties);
         assertTrue(PropertyAnimator.isAnimating(mView, mProperty));
     }
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index 30132f7..08adda3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -1,7 +1,8 @@
 package com.android.systemui.communal.data.repository
 
 import com.android.systemui.communal.data.model.CommunalWidgetMetadata
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 
@@ -11,7 +12,14 @@
     override val stopwatchAppWidgetInfo: Flow<CommunalAppWidgetInfo?> = _stopwatchAppWidgetInfo
     override var communalWidgetAllowlist: List<CommunalWidgetMetadata> = emptyList()
 
+    private val _communalWidgets = MutableStateFlow<List<CommunalWidgetContentModel>>(emptyList())
+    override val communalWidgets: Flow<List<CommunalWidgetContentModel>> = _communalWidgets
+
     fun setStopwatchAppWidgetInfo(appWidgetInfo: CommunalAppWidgetInfo) {
         _stopwatchAppWidgetInfo.value = appWidgetInfo
     }
+
+    fun setCommunalWidgets(inventory: List<CommunalWidgetContentModel>) {
+        _communalWidgets.value = inventory
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
index e160548..71e2bc1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -42,7 +42,7 @@
         _transitions.emit(step)
     }
 
-    override fun startTransition(info: TransitionInfo, resetIfCanceled: Boolean): UUID? {
+    override fun startTransition(info: TransitionInfo): UUID? {
         return if (info.animator == null) UUID.randomUUID() else null
     }
 
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 1a735f8..75ecdb7 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -1,32 +1,6 @@
 package: "com.android.server.accessibility"
 
-flag {
-    name: "proxy_use_apps_on_virtual_device_listener"
-    namespace: "accessibility"
-    description: "Fixes race condition described in b/286587811"
-    bug: "286587811"
-}
-
-flag {
-    name: "enable_magnification_multiple_finger_multiple_tap_gesture"
-    namespace: "accessibility"
-    description: "Whether to enable multi-finger-multi-tap gesture for magnification"
-    bug: "257274411"
-}
-
-flag {
-    name: "enable_magnification_joystick"
-    namespace: "accessibility"
-    description: "Whether to enable joystick controls for magnification"
-    bug: "297211257"
-}
-
-flag {
-    name: "send_a11y_events_based_on_state"
-    namespace: "accessibility"
-    description: "Sends accessibility events in TouchExplorer#onAccessibilityEvent based on internal state to keep it consistent. This reduces test flakiness."
-    bug: "295575684"
-}
+# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
 
 flag {
     name: "add_window_token_without_lock"
@@ -36,20 +10,6 @@
 }
 
 flag {
-    name: "pinch_zoom_zero_min_span"
-    namespace: "accessibility"
-    description: "Whether to set min span of ScaleGestureDetector to zero."
-    bug: "295327792"
-}
-
-flag {
-    name: "disable_continuous_shortcut_on_force_stop"
-    namespace: "accessibility"
-    description: "When a package is force stopped, remove the button shortcuts of any continuously-running shortcuts."
-    bug: "198018180"
-}
-
-flag {
     name: "deprecate_package_list_observer"
     namespace: "accessibility"
     description: "Stops using the deprecated PackageListObserver."
@@ -57,10 +17,38 @@
 }
 
 flag {
-    name: "scan_packages_without_lock"
+    name: "disable_continuous_shortcut_on_force_stop"
     namespace: "accessibility"
-    description: "Scans packages for accessibility service/activity info without holding the A11yMS lock"
-    bug: "295969873"
+    description: "When a package is force stopped, remove the button shortcuts of any continuously-running shortcuts."
+    bug: "198018180"
+}
+
+flag {
+    name: "enable_magnification_joystick"
+    namespace: "accessibility"
+    description: "Whether to enable joystick controls for magnification"
+    bug: "297211257"
+}
+
+flag {
+    name: "enable_magnification_multiple_finger_multiple_tap_gesture"
+    namespace: "accessibility"
+    description: "Whether to enable multi-finger-multi-tap gesture for magnification"
+    bug: "257274411"
+}
+
+flag {
+    name: "pinch_zoom_zero_min_span"
+    namespace: "accessibility"
+    description: "Whether to set min span of ScaleGestureDetector to zero."
+    bug: "295327792"
+}
+
+flag {
+    name: "proxy_use_apps_on_virtual_device_listener"
+    namespace: "accessibility"
+    description: "Fixes race condition described in b/286587811"
+    bug: "286587811"
 }
 
 flag {
@@ -69,3 +57,17 @@
     description: "Reduces touch exploration sensitivity by only sending a hover event when the ifnger has moved the amount of pixels defined by the system's touch slop."
     bug: "303677860"
 }
+
+flag {
+    name: "scan_packages_without_lock"
+    namespace: "accessibility"
+    description: "Scans packages for accessibility service/activity info without holding the A11yMS lock"
+    bug: "295969873"
+}
+
+flag {
+    name: "send_a11y_events_based_on_state"
+    namespace: "accessibility"
+    description: "Sends accessibility events in TouchExplorer#onAccessibilityEvent based on internal state to keep it consistent. This reduces test flakiness."
+    bug: "295575684"
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index e65a185..87f9cf1 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 @@
         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 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 @@
                     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 898cdcc..b696998 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -136,6 +136,7 @@
         ":display-device-config",
         ":display-layout-config",
         ":device-state-config",
+        ":framework-core-nfc-infcadapter-sources",
         "java/com/android/server/EventLogTags.logtags",
         "java/com/android/server/am/EventLogTags.logtags",
         "java/com/android/server/wm/EventLogTags.logtags",
@@ -195,6 +196,7 @@
         "android.hardware.rebootescrow-V1-java",
         "android.hardware.power.stats-V2-java",
         "android.hidl.manager-V1.2-java",
+        "android.nfc.flags-aconfig-java",
         "cbor-java",
         "icu4j_calendar_astronomer",
         "android.security.aaid_aidl-java",
diff --git a/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java b/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java
index 9b5f18c..710278d 100644
--- a/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java
+++ b/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java
@@ -16,6 +16,8 @@
 
 package com.android.server.am;
 
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+
 import android.content.Context;
 import android.content.DialogInterface;
 import android.os.Handler;
@@ -54,6 +56,7 @@
         setButton(DialogInterface.BUTTON_POSITIVE, "Force Close", mHandler.obtainMessage(1, app));
         setTitle("Waiting For Debugger");
         WindowManager.LayoutParams attrs = getWindow().getAttributes();
+        attrs.privateFlags |= SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
         attrs.setTitle("Waiting For Debugger: " + app.info.processName);
         getWindow().setAttributes(attrs);
     }
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 2d231b3..aa788eb 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -118,6 +118,7 @@
     // The list is sorted.
     @VisibleForTesting
     static final String[] sDeviceConfigAconfigScopes = new String[] {
+        "accessibility",
         "android_core_networking",
         "angle",
         "arc_next",
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index bb9ea28..cbaf05b 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 @@
      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/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 35260ed..7abd9c7 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -39,10 +39,9 @@
 import android.media.ISpatializerHeadTrackingModeCallback;
 import android.media.ISpatializerOutputCallback;
 import android.media.MediaMetrics;
-import android.media.SpatializationLevel;
-import android.media.SpatializationMode;
 import android.media.Spatializer;
-import android.media.SpatializerHeadTrackingMode;
+import android.media.audio.common.HeadTracking;
+import android.media.audio.common.Spatialization;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.text.TextUtils;
@@ -84,22 +83,22 @@
 
     /*package*/ static final SparseIntArray SPAT_MODE_FOR_DEVICE_TYPE = new SparseIntArray(14) {
         {
-            append(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, SpatializationMode.SPATIALIZER_TRANSAURAL);
-            append(AudioDeviceInfo.TYPE_WIRED_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL);
-            append(AudioDeviceInfo.TYPE_WIRED_HEADPHONES, SpatializationMode.SPATIALIZER_BINAURAL);
+            append(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, Spatialization.Mode.TRANSAURAL);
+            append(AudioDeviceInfo.TYPE_WIRED_HEADSET, Spatialization.Mode.BINAURAL);
+            append(AudioDeviceInfo.TYPE_WIRED_HEADPHONES, Spatialization.Mode.BINAURAL);
             // assumption for A2DP: mostly headsets
-            append(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, SpatializationMode.SPATIALIZER_BINAURAL);
-            append(AudioDeviceInfo.TYPE_DOCK, SpatializationMode.SPATIALIZER_TRANSAURAL);
-            append(AudioDeviceInfo.TYPE_USB_ACCESSORY, SpatializationMode.SPATIALIZER_TRANSAURAL);
-            append(AudioDeviceInfo.TYPE_USB_DEVICE, SpatializationMode.SPATIALIZER_TRANSAURAL);
-            append(AudioDeviceInfo.TYPE_USB_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL);
-            append(AudioDeviceInfo.TYPE_LINE_ANALOG, SpatializationMode.SPATIALIZER_TRANSAURAL);
-            append(AudioDeviceInfo.TYPE_LINE_DIGITAL, SpatializationMode.SPATIALIZER_TRANSAURAL);
-            append(AudioDeviceInfo.TYPE_AUX_LINE, SpatializationMode.SPATIALIZER_TRANSAURAL);
-            append(AudioDeviceInfo.TYPE_BLE_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL);
-            append(AudioDeviceInfo.TYPE_BLE_SPEAKER, SpatializationMode.SPATIALIZER_TRANSAURAL);
+            append(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, Spatialization.Mode.BINAURAL);
+            append(AudioDeviceInfo.TYPE_DOCK, Spatialization.Mode.TRANSAURAL);
+            append(AudioDeviceInfo.TYPE_USB_ACCESSORY, Spatialization.Mode.TRANSAURAL);
+            append(AudioDeviceInfo.TYPE_USB_DEVICE, Spatialization.Mode.TRANSAURAL);
+            append(AudioDeviceInfo.TYPE_USB_HEADSET, Spatialization.Mode.BINAURAL);
+            append(AudioDeviceInfo.TYPE_LINE_ANALOG, Spatialization.Mode.TRANSAURAL);
+            append(AudioDeviceInfo.TYPE_LINE_DIGITAL, Spatialization.Mode.TRANSAURAL);
+            append(AudioDeviceInfo.TYPE_AUX_LINE, Spatialization.Mode.TRANSAURAL);
+            append(AudioDeviceInfo.TYPE_BLE_HEADSET, Spatialization.Mode.BINAURAL);
+            append(AudioDeviceInfo.TYPE_BLE_SPEAKER, Spatialization.Mode.TRANSAURAL);
             // assumption that BLE broadcast would be mostly consumed on headsets
-            append(AudioDeviceInfo.TYPE_BLE_BROADCAST, SpatializationMode.SPATIALIZER_BINAURAL);
+            append(AudioDeviceInfo.TYPE_BLE_BROADCAST, Spatialization.Mode.BINAURAL);
         }
     };
 
@@ -226,12 +225,12 @@
                 ArrayList<Integer> list = new ArrayList<>(0);
                 for (byte value : values) {
                     switch (value) {
-                        case SpatializerHeadTrackingMode.OTHER:
-                        case SpatializerHeadTrackingMode.DISABLED:
+                        case HeadTracking.Mode.OTHER:
+                        case HeadTracking.Mode.DISABLED:
                             // not expected here, skip
                             break;
-                        case SpatializerHeadTrackingMode.RELATIVE_WORLD:
-                        case SpatializerHeadTrackingMode.RELATIVE_SCREEN:
+                        case HeadTracking.Mode.RELATIVE_WORLD:
+                        case HeadTracking.Mode.RELATIVE_SCREEN:
                             list.add(headTrackingModeTypeToSpatializerInt(value));
                             break;
                         default:
@@ -254,10 +253,10 @@
             byte[] spatModes = spat.getSupportedModes();
             for (byte mode : spatModes) {
                 switch (mode) {
-                    case SpatializationMode.SPATIALIZER_BINAURAL:
+                    case Spatialization.Mode.BINAURAL:
                         mBinauralSupported = true;
                         break;
-                    case SpatializationMode.SPATIALIZER_TRANSAURAL:
+                    case Spatialization.Mode.TRANSAURAL:
                         mTransauralSupported = true;
                         break;
                     default:
@@ -274,8 +273,8 @@
             // initialize list of compatible devices
             for (int i = 0; i < SPAT_MODE_FOR_DEVICE_TYPE.size(); i++) {
                 int mode = SPAT_MODE_FOR_DEVICE_TYPE.valueAt(i);
-                if ((mode == (int) SpatializationMode.SPATIALIZER_BINAURAL && mBinauralSupported)
-                        || (mode == (int) SpatializationMode.SPATIALIZER_TRANSAURAL
+                if ((mode == (int) Spatialization.Mode.BINAURAL && mBinauralSupported)
+                        || (mode == (int) Spatialization.Mode.TRANSAURAL
                             && mTransauralSupported)) {
                     mSACapableDeviceTypes.add(SPAT_MODE_FOR_DEVICE_TYPE.keyAt(i));
                 }
@@ -577,9 +576,9 @@
 
         int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(device.getDeviceType(),
                 Integer.MIN_VALUE);
-        device.setSAEnabled(spatMode == SpatializationMode.SPATIALIZER_BINAURAL
+        device.setSAEnabled(spatMode == Spatialization.Mode.BINAURAL
                 ? mBinauralEnabledDefault
-                : spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL
+                : spatMode == Spatialization.Mode.TRANSAURAL
                         ? mTransauralEnabledDefault
                         : false);
         device.setHeadTrackerEnabled(mHeadTrackingEnabledDefault);
@@ -629,9 +628,9 @@
         if (isBluetoothDevice(internalDeviceType)) return deviceType;
 
         final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE);
-        if (spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL) {
+        if (spatMode == Spatialization.Mode.TRANSAURAL) {
             return AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
-        } else if (spatMode == SpatializationMode.SPATIALIZER_BINAURAL) {
+        } else if (spatMode == Spatialization.Mode.BINAURAL) {
             return AudioDeviceInfo.TYPE_WIRED_HEADPHONES;
         }
         return AudioDeviceInfo.TYPE_UNKNOWN;
@@ -690,8 +689,7 @@
             // since their physical characteristics are unknown
             if (deviceState.getAudioDeviceCategory() == AUDIO_DEVICE_CATEGORY_UNKNOWN
                     || deviceState.getAudioDeviceCategory() == AUDIO_DEVICE_CATEGORY_HEADPHONES) {
-                available = (spatMode == SpatializationMode.SPATIALIZER_BINAURAL)
-                        && mBinauralSupported;
+                available = (spatMode == Spatialization.Mode.BINAURAL) && mBinauralSupported;
             } else {
                 available = false;
             }
@@ -804,8 +802,8 @@
         // not be included.
         final byte modeForDevice = (byte) SPAT_MODE_FOR_DEVICE_TYPE.get(ada.getType(),
                 /*default when type not found*/ -1);
-        if ((modeForDevice == SpatializationMode.SPATIALIZER_BINAURAL && mBinauralSupported)
-                || (modeForDevice == SpatializationMode.SPATIALIZER_TRANSAURAL
+        if ((modeForDevice == Spatialization.Mode.BINAURAL && mBinauralSupported)
+                || (modeForDevice == Spatialization.Mode.TRANSAURAL
                         && mTransauralSupported)) {
             return true;
         }
@@ -1479,7 +1477,7 @@
     }
 
     synchronized void onInitSensors() {
-        final boolean init = mFeatureEnabled && (mSpatLevel != SpatializationLevel.NONE);
+        final boolean init = mFeatureEnabled && (mSpatLevel != Spatialization.Level.NONE);
         final String action = init ? "initializing" : "releasing";
         if (mSpat == null) {
             logloge("not " + action + " sensors, null spatializer");
@@ -1545,13 +1543,13 @@
     // SDK <-> AIDL converters
     private static int headTrackingModeTypeToSpatializerInt(byte mode) {
         switch (mode) {
-            case SpatializerHeadTrackingMode.OTHER:
+            case HeadTracking.Mode.OTHER:
                 return Spatializer.HEAD_TRACKING_MODE_OTHER;
-            case SpatializerHeadTrackingMode.DISABLED:
+            case HeadTracking.Mode.DISABLED:
                 return Spatializer.HEAD_TRACKING_MODE_DISABLED;
-            case SpatializerHeadTrackingMode.RELATIVE_WORLD:
+            case HeadTracking.Mode.RELATIVE_WORLD:
                 return Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD;
-            case SpatializerHeadTrackingMode.RELATIVE_SCREEN:
+            case HeadTracking.Mode.RELATIVE_SCREEN:
                 return Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE;
             default:
                 throw (new IllegalArgumentException("Unexpected head tracking mode:" + mode));
@@ -1561,13 +1559,13 @@
     private static byte spatializerIntToHeadTrackingModeType(int sdkMode) {
         switch (sdkMode) {
             case Spatializer.HEAD_TRACKING_MODE_OTHER:
-                return SpatializerHeadTrackingMode.OTHER;
+                return HeadTracking.Mode.OTHER;
             case Spatializer.HEAD_TRACKING_MODE_DISABLED:
-                return SpatializerHeadTrackingMode.DISABLED;
+                return HeadTracking.Mode.DISABLED;
             case Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD:
-                return SpatializerHeadTrackingMode.RELATIVE_WORLD;
+                return HeadTracking.Mode.RELATIVE_WORLD;
             case Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE:
-                return SpatializerHeadTrackingMode.RELATIVE_SCREEN;
+                return HeadTracking.Mode.RELATIVE_SCREEN;
             default:
                 throw (new IllegalArgumentException("Unexpected head tracking mode:" + sdkMode));
         }
@@ -1575,11 +1573,11 @@
 
     private static int spatializationLevelToSpatializerInt(byte level) {
         switch (level) {
-            case SpatializationLevel.NONE:
+            case Spatialization.Level.NONE:
                 return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
-            case SpatializationLevel.SPATIALIZER_MULTICHANNEL:
+            case Spatialization.Level.MULTICHANNEL:
                 return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL;
-            case SpatializationLevel.SPATIALIZER_MCHAN_BED_PLUS_OBJECTS:
+            case Spatialization.Level.BED_PLUS_OBJECTS:
                 return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MCHAN_BED_PLUS_OBJECTS;
             default:
                 throw (new IllegalArgumentException("Unexpected spatializer level:" + level));
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 3d347be..f9bc8dc 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -53,6 +53,8 @@
 import android.hardware.usb.UsbManager;
 import android.media.AudioManager;
 import android.nfc.INfcAdapter;
+import android.nfc.NfcAdapter;
+import android.nfc.NfcManager;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.HandlerExecutor;
@@ -163,10 +165,6 @@
      * SCALER_ROTATE_AND_CROP_NONE  -> Always return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE
      */
 
-    // Flags arguments to NFC adapter to enable/disable NFC
-    public static final int DISABLE_POLLING_FLAGS = 0x1000;
-    public static final int ENABLE_POLLING_FLAGS = 0x0000;
-
     // Handler message codes
     private static final int MSG_SWITCH_USER = 1;
     private static final int MSG_NOTIFY_DEVICE_STATE = 2;
@@ -216,7 +214,6 @@
     private final List<CameraUsageEvent> mCameraUsageHistory = new ArrayList<>();
 
     private static final String NFC_NOTIFICATION_PROP = "ro.camera.notify_nfc";
-    private static final String NFC_SERVICE_BINDER_NAME = "nfc";
     private static final IBinder nfcInterfaceToken = new Binder();
 
     private final boolean mNotifyNfc;
@@ -1274,8 +1271,13 @@
         }
     }
 
-    private void notifyNfcService(boolean enablePolling) {
-
+    // TODO(b/303286040): Remove the raw INfcAdapter usage once |ENABLE_NFC_MAINLINE_FLAG| is
+    // rolled out.
+    private static final String NFC_SERVICE_BINDER_NAME = "nfc";
+    // Flags arguments to NFC adapter to enable/disable NFC
+    public static final int DISABLE_POLLING_FLAGS = 0x1000;
+    public static final int ENABLE_POLLING_FLAGS = 0x0000;
+    private void setNfcReaderModeUsingINfcAdapter(boolean enablePolling) {
         IBinder nfcServiceBinder = getBinderService(NFC_SERVICE_BINDER_NAME);
         if (nfcServiceBinder == null) {
             Slog.w(TAG, "Could not connect to NFC service to notify it of camera state");
@@ -1291,6 +1293,25 @@
         }
     }
 
+    private void notifyNfcService(boolean enablePolling) {
+        if (android.nfc.Flags.enableNfcMainline()) {
+            NfcManager nfcManager = mContext.getSystemService(NfcManager.class);
+            if (nfcManager == null) {
+                Slog.w(TAG, "Could not connect to NFC service to notify it of camera state");
+                return;
+            }
+            NfcAdapter nfcAdapter = nfcManager.getDefaultAdapter();
+            if (nfcAdapter == null) {
+                Slog.w(TAG, "Could not connect to NFC service to notify it of camera state");
+                return;
+            }
+            if (DEBUG) Slog.v(TAG, "Setting NFC reader mode. enablePolling: " + enablePolling);
+            nfcAdapter.setReaderMode(enablePolling);
+        } else {
+            setNfcReaderModeUsingINfcAdapter(enablePolling);
+        }
+    }
+
     private static int[] toArray(Collection<Integer> c) {
         int len = c.size();
         int[] ret = new int[len];
diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
index 425a1af..6695801 100644
--- a/services/core/java/com/android/server/display/DisplayBrightnessState.java
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -27,6 +27,8 @@
  * the DisplayBrightnessModeStrategies when updating the brightness.
  */
 public final class DisplayBrightnessState {
+    public static final float CUSTOM_ANIMATION_RATE_NOT_SET = -1f;
+
     private final float mBrightness;
     private final float mSdrBrightness;
 
@@ -37,6 +39,8 @@
 
     private final boolean mIsSlowChange;
 
+    private final float mCustomAnimationRate;
+
     private DisplayBrightnessState(Builder builder) {
         mBrightness = builder.getBrightness();
         mSdrBrightness = builder.getSdrBrightness();
@@ -45,6 +49,7 @@
         mShouldUseAutoBrightness = builder.getShouldUseAutoBrightness();
         mIsSlowChange = builder.isSlowChange();
         mMaxBrightness = builder.getMaxBrightness();
+        mCustomAnimationRate = builder.getCustomAnimationRate();
     }
 
     /**
@@ -97,7 +102,12 @@
         return mMaxBrightness;
     }
 
-
+    /**
+     * @return custom animation rate
+     */
+    public float getCustomAnimationRate() {
+        return mCustomAnimationRate;
+    }
 
     @Override
     public String toString() {
@@ -112,6 +122,7 @@
         stringBuilder.append(getShouldUseAutoBrightness());
         stringBuilder.append("\n    isSlowChange:").append(mIsSlowChange);
         stringBuilder.append("\n    maxBrightness:").append(mMaxBrightness);
+        stringBuilder.append("\n    customAnimationRate:").append(mCustomAnimationRate);
         return stringBuilder.toString();
     }
 
@@ -137,13 +148,14 @@
                         otherState.getDisplayBrightnessStrategyName())
                 && mShouldUseAutoBrightness == otherState.getShouldUseAutoBrightness()
                 && mIsSlowChange == otherState.isSlowChange()
-                && mMaxBrightness == otherState.getMaxBrightness();
+                && mMaxBrightness == otherState.getMaxBrightness()
+                && mCustomAnimationRate == otherState.getCustomAnimationRate();
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mBrightness, mSdrBrightness, mBrightnessReason,
-                mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness);
+                mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness, mCustomAnimationRate);
     }
 
     /**
@@ -164,6 +176,7 @@
         private boolean mShouldUseAutoBrightness;
         private boolean mIsSlowChange;
         private float mMaxBrightness;
+        private float mCustomAnimationRate = CUSTOM_ANIMATION_RATE_NOT_SET;
 
         /**
          * Create a builder starting with the values from the specified {@link
@@ -180,6 +193,7 @@
             builder.setShouldUseAutoBrightness(state.getShouldUseAutoBrightness());
             builder.setIsSlowChange(state.isSlowChange());
             builder.setMaxBrightness(state.getMaxBrightness());
+            builder.setCustomAnimationRate(state.getCustomAnimationRate());
             return builder;
         }
 
@@ -303,6 +317,22 @@
             return mMaxBrightness;
         }
 
+
+        /**
+         * See {@link DisplayBrightnessState#getCustomAnimationRate()}.
+         */
+        public Builder setCustomAnimationRate(float animationRate) {
+            this.mCustomAnimationRate = animationRate;
+            return this;
+        }
+
+        /**
+         * See {@link DisplayBrightnessState#getCustomAnimationRate()}.
+         */
+        public float getCustomAnimationRate() {
+            return mCustomAnimationRate;
+        }
+
         /**
          * This is used to construct an immutable DisplayBrightnessState object from its builder
          */
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index cb2302a..a788968 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -586,7 +586,8 @@
         mSystemReady = false;
         mConfigParameterProvider = new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
         mExtraDisplayLoggingPackageName = DisplayProperties.debug_vri_package().orElse(null);
-        mExtraDisplayEventLogging = !TextUtils.isEmpty(mExtraDisplayLoggingPackageName);
+        // TODO: b/306170135 - return TextUtils package name check instead
+        mExtraDisplayEventLogging = true;
     }
 
     public void setupSchedulerPolicies() {
@@ -2933,8 +2934,15 @@
     // Send a display event if the display is enabled
     private void sendDisplayEventIfEnabledLocked(@NonNull LogicalDisplay display,
                                                  @DisplayEvent int event) {
+        final boolean displayIsEnabled = display.isEnabledLocked();
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+            Trace.instant(Trace.TRACE_TAG_POWER,
+                    "sendDisplayEventLocked#event=" + event + ",displayEnabled="
+                            + displayIsEnabled);
+        }
+
         // Only send updates outside of DisplayManagerService for enabled displays
-        if (display.isEnabledLocked()) {
+        if (displayIsEnabled) {
             sendDisplayEventLocked(display, event);
         } else if (mExtraDisplayEventLogging) {
             Slog.i(TAG, "Not Sending Display Event; display is not enabled: " + display);
@@ -2991,7 +2999,11 @@
                     + displayId + ", event=" + event
                     + (uids != null ? ", uids=" + uids : ""));
         }
-
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+            Trace.instant(Trace.TRACE_TAG_POWER,
+                    "deliverDisplayEvent#event=" + event + ",displayId="
+                            + displayId   + (uids != null ? ", uids=" + uids : ""));
+        }
         // Grab the lock and copy the callbacks.
         final int count;
         synchronized (mSyncRoot) {
@@ -3031,7 +3043,8 @@
     }
 
     private boolean extraLogging(String packageName) {
-        return mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals(packageName);
+        // TODO: b/306170135 - return mExtraDisplayLoggingPackageName & package name check instead
+        return true;
     }
 
     // Runs on Handler thread.
@@ -3498,10 +3511,13 @@
 
         @Override
         public void binderDied() {
-            if (DEBUG || mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals(
-                    mPackageName)) {
+            if (DEBUG || extraLogging(mPackageName)) {
                 Slog.d(TAG, "Display listener for pid " + mPid + " died.");
             }
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+                Trace.instant(Trace.TRACE_TAG_POWER,
+                        "displayManagerBinderDied#mPid=" + mPid);
+            }
             onCallbackDied(this);
         }
 
@@ -3510,11 +3526,15 @@
          */
         public boolean notifyDisplayEventAsync(int displayId, @DisplayEvent int event) {
             if (!shouldSendEvent(event)) {
-                if (mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals(
-                        mPackageName)) {
+                if (extraLogging(mPackageName)) {
                     Slog.i(TAG,
                             "Not sending displayEvent: " + event + " due to mask:" + mEventsMask);
                 }
+                if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+                    Trace.instant(Trace.TRACE_TAG_POWER,
+                            "notifyDisplayEventAsync#notSendingEvent=" + event + ",mEventsMask="
+                                    + mEventsMask);
+                }
                 return true;
             }
 
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 7d9c018..0f00027 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -561,8 +561,9 @@
                         brightnessSetting, () -> postBrightnessChangeRunnable(),
                         new HandlerExecutor(mHandler));
 
-        mBrightnessClamperController = new BrightnessClamperController(mHandler,
-                modeChangeCallback::run, new BrightnessClamperController.DisplayDeviceData(
+        mBrightnessClamperController = mInjector.getBrightnessClamperController(
+                mHandler, modeChangeCallback::run,
+                new BrightnessClamperController.DisplayDeviceData(
                 mUniqueDisplayId,
                 mThermalBrightnessThrottlingDataId,
                 logicalDisplay.getPowerThrottlingDataIdLocked(),
@@ -1353,6 +1354,8 @@
         float rawBrightnessState = displayBrightnessState.getBrightness();
         mBrightnessReasonTemp.set(displayBrightnessState.getBrightnessReason());
         boolean slowChange = displayBrightnessState.isSlowChange();
+        // custom transition duration
+        float customAnimationRate = displayBrightnessState.getCustomAnimationRate();
 
         // Set up the ScreenOff controller used when coming out of SCREEN_OFF and the ALS sensor
         // doesn't yet have a valid lux value to use with auto-brightness.
@@ -1485,6 +1488,9 @@
 
         brightnessState = clampedState.getBrightness();
         slowChange = clampedState.isSlowChange();
+        // faster rate wins, at this point customAnimationRate == -1, strategy does not control
+        // customAnimationRate. Should be revisited if strategy start setting this value
+        customAnimationRate = Math.max(customAnimationRate, clampedState.getCustomAnimationRate());
         mBrightnessReasonTemp.addModifier(clampedState.getBrightnessReason().getModifier());
 
         if (updateScreenBrightnessSetting) {
@@ -1553,9 +1559,6 @@
             // allowed range.
             float animateValue = clampScreenBrightness(brightnessState);
 
-            // custom transition duration
-            float customTransitionRate = -1f;
-
             // If there are any HDR layers on the screen, we have a special brightness value that we
             // use instead. We still preserve the calculated brightness for Standard Dynamic Range
             // (SDR) layers, but the main brightness value will be the one for HDR.
@@ -1570,10 +1573,21 @@
                 // We want to scale HDR brightness level with the SDR level, we also need to restore
                 // SDR brightness immediately when entering dim or low power mode.
                 animateValue = mBrightnessRangeController.getHdrBrightnessValue();
-                customTransitionRate = mBrightnessRangeController.getHdrTransitionRate();
+                customAnimationRate = Math.max(customAnimationRate,
+                        mBrightnessRangeController.getHdrTransitionRate());
                 mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_HDR);
             }
 
+            // if doze or suspend state is requested, we want to finish brightnes animation fast
+            // to allow state animation to start
+            if (mPowerRequest.policy == DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE
+                    && (mPowerRequest.dozeScreenState == Display.STATE_UNKNOWN  // dozing
+                    || mPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND
+                    || mPowerRequest.dozeScreenState == Display.STATE_ON_SUSPEND)) {
+                customAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
+                slowChange = false;
+            }
+
             final float currentBrightness = mPowerState.getScreenBrightness();
             final float currentSdrBrightness = mPowerState.getSdrScreenBrightness();
 
@@ -1601,9 +1615,9 @@
                 if (skipAnimation) {
                     animateScreenBrightness(animateValue, sdrAnimateValue,
                             SCREEN_ANIMATION_RATE_MINIMUM);
-                } else if (customTransitionRate > 0) {
+                } else if (customAnimationRate > 0) {
                     animateScreenBrightness(animateValue, sdrAnimateValue,
-                            customTransitionRate, /* ignoreAnimationLimits = */true);
+                            customAnimationRate, /* ignoreAnimationLimits = */true);
                 } else {
                     boolean isIncreasing = animateValue > currentBrightness;
                     final float rampSpeed;
@@ -3059,6 +3073,15 @@
                     modeChangeCallback, displayDeviceConfig, handler, flags, displayToken, info);
         }
 
+        BrightnessClamperController getBrightnessClamperController(Handler handler,
+                BrightnessClamperController.ClamperChangeListener clamperChangeListener,
+                BrightnessClamperController.DisplayDeviceData data, Context context,
+                DisplayManagerFlags flags) {
+
+            return new BrightnessClamperController(handler, clamperChangeListener, data, context,
+                    flags);
+        }
+
         DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
                 SensorManager sensorManager, Resources resources) {
             return DisplayWhiteBalanceFactory.create(handler,
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
index 68f72d3..dfcda40 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
@@ -19,6 +19,8 @@
 import android.annotation.NonNull;
 import android.os.PowerManager;
 
+import com.android.server.display.DisplayBrightnessState;
+
 import java.io.PrintWriter;
 
 /**
@@ -33,6 +35,10 @@
         return mBrightnessCap;
     }
 
+    float getCustomAnimationRate() {
+        return DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
+    }
+
     boolean isActive() {
         return mIsActive;
     }
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index 787f786..b574919 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -51,6 +51,7 @@
  */
 public class BrightnessClamperController {
     private static final String TAG = "BrightnessClamperController";
+
     private final DeviceConfigParameterProvider mDeviceConfigParameterProvider;
     private final Handler mHandler;
     private final ClamperChangeListener mClamperChangeListenerExternal;
@@ -60,6 +61,8 @@
     private final List<BrightnessModifier> mModifiers;
     private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener;
     private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
+
+    private float mCustomAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
     @Nullable
     private Type mClamperType = null;
     private boolean mClamperApplied = false;
@@ -113,6 +116,7 @@
         builder.setIsSlowChange(slowChange);
         builder.setBrightness(cappedBrightness);
         builder.setMaxBrightness(mBrightnessCap);
+        builder.setCustomAnimationRate(mCustomAnimationRate);
 
         if (mClamperType != null) {
             builder.getBrightnessReason().addModifier(BrightnessReason.MODIFIER_THROTTLED);
@@ -182,6 +186,7 @@
     private void recalculateBrightnessCap() {
         float brightnessCap = PowerManager.BRIGHTNESS_MAX;
         Type clamperType = null;
+        float customAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
 
         BrightnessClamper<?> minClamper = mClampers.stream()
                 .filter(BrightnessClamper::isActive)
@@ -191,11 +196,14 @@
         if (minClamper != null) {
             brightnessCap = minClamper.getBrightnessCap();
             clamperType = minClamper.getType();
+            customAnimationRate = minClamper.getCustomAnimationRate();
         }
 
-        if (mBrightnessCap != brightnessCap || mClamperType != clamperType) {
+        if (mBrightnessCap != brightnessCap || mClamperType != clamperType
+                || mCustomAnimationRate != customAnimationRate) {
             mBrightnessCap = brightnessCap;
             mClamperType = clamperType;
+            mCustomAnimationRate = customAnimationRate;
             mClamperChangeListenerExternal.onChanged();
         }
 
diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
index 39f0b13..200d88a 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
@@ -24,7 +24,6 @@
 import android.view.SurfaceControlHdrLayerInfoListener;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.display.BrightnessUtils;
 import com.android.server.display.config.HdrBrightnessData;
 
 import java.io.PrintWriter;
@@ -176,21 +175,14 @@
         } else if (mDesiredMaxBrightness != expectedMaxBrightness) {
             mDesiredMaxBrightness = expectedMaxBrightness;
             long debounceTime;
-            long transitionDuration;
             if (mDesiredMaxBrightness > mMaxBrightness) {
                 debounceTime = mHdrBrightnessData.mBrightnessIncreaseDebounceMillis;
-                transitionDuration = mHdrBrightnessData.mBrightnessIncreaseDurationMillis;
+                mDesiredTransitionRate = mHdrBrightnessData.mScreenBrightnessRampIncrease;
             } else {
                 debounceTime = mHdrBrightnessData.mBrightnessDecreaseDebounceMillis;
-                transitionDuration = mHdrBrightnessData.mBrightnessDecreaseDurationMillis;
+                mDesiredTransitionRate = mHdrBrightnessData.mScreenBrightnessRampDecrease;
             }
 
-            float maxHlg = BrightnessUtils.convertLinearToGamma(mMaxBrightness);
-            float desiredMaxHlg = BrightnessUtils.convertLinearToGamma(mDesiredMaxBrightness);
-
-            mDesiredTransitionRate = Math.abs(
-                    (maxHlg - desiredMaxHlg) * 1000f / transitionDuration);
-
             mHandler.removeCallbacks(mDebouncer);
             mHandler.postDelayed(mDebouncer, debounceTime);
         }
diff --git a/services/core/java/com/android/server/display/config/HdrBrightnessData.java b/services/core/java/com/android/server/display/config/HdrBrightnessData.java
index 48d671d..837fbf7 100644
--- a/services/core/java/com/android/server/display/config/HdrBrightnessData.java
+++ b/services/core/java/com/android/server/display/config/HdrBrightnessData.java
@@ -40,9 +40,9 @@
     public final long mBrightnessIncreaseDebounceMillis;
 
     /**
-     * Brightness increase animation duration
+     * Brightness increase animation speed
      */
-    public final long mBrightnessIncreaseDurationMillis;
+    public final float mScreenBrightnessRampIncrease;
 
     /**
      * Debounce time for brightness decrease
@@ -50,19 +50,19 @@
     public final long mBrightnessDecreaseDebounceMillis;
 
     /**
-     * Brightness decrease animation duration
+     * Brightness decrease animation speed
      */
-    public final long mBrightnessDecreaseDurationMillis;
+    public final float mScreenBrightnessRampDecrease;
 
     @VisibleForTesting
     public HdrBrightnessData(Map<Float, Float> maxBrightnessLimits,
-            long brightnessIncreaseDebounceMillis, long brightnessIncreaseDurationMillis,
-            long brightnessDecreaseDebounceMillis, long brightnessDecreaseDurationMillis) {
+            long brightnessIncreaseDebounceMillis, float screenBrightnessRampIncrease,
+            long brightnessDecreaseDebounceMillis, float screenBrightnessRampDecrease) {
         mMaxBrightnessLimits = maxBrightnessLimits;
         mBrightnessIncreaseDebounceMillis = brightnessIncreaseDebounceMillis;
-        mBrightnessIncreaseDurationMillis = brightnessIncreaseDurationMillis;
+        mScreenBrightnessRampIncrease = screenBrightnessRampIncrease;
         mBrightnessDecreaseDebounceMillis = brightnessDecreaseDebounceMillis;
-        mBrightnessDecreaseDurationMillis = brightnessDecreaseDurationMillis;
+        mScreenBrightnessRampDecrease = screenBrightnessRampDecrease;
     }
 
     @Override
@@ -70,9 +70,9 @@
         return "HdrBrightnessData {"
                 + "mMaxBrightnessLimits: " + mMaxBrightnessLimits
                 + ", mBrightnessIncreaseDebounceMillis: " + mBrightnessIncreaseDebounceMillis
-                + ", mBrightnessIncreaseDurationMillis: " + mBrightnessIncreaseDurationMillis
+                + ", mScreenBrightnessRampIncrease: " + mScreenBrightnessRampIncrease
                 + ", mBrightnessDecreaseDebounceMillis: " + mBrightnessDecreaseDebounceMillis
-                + ", mBrightnessDecreaseDurationMillis: " + mBrightnessDecreaseDurationMillis
+                + ", mScreenBrightnessRampDecrease: " + mScreenBrightnessRampDecrease
                 + "} ";
     }
 
@@ -94,8 +94,8 @@
 
         return new HdrBrightnessData(brightnessLimits,
                 hdrConfig.getBrightnessIncreaseDebounceMillis().longValue(),
-                hdrConfig.getBrightnessIncreaseDurationMillis().longValue(),
+                hdrConfig.getScreenBrightnessRampIncrease().floatValue(),
                 hdrConfig.getBrightnessDecreaseDebounceMillis().longValue(),
-                hdrConfig.getBrightnessDecreaseDurationMillis().longValue());
+                hdrConfig.getScreenBrightnessRampDecrease().floatValue());
     }
 }
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 0e8f907..2c59511 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -268,7 +268,14 @@
             }
             if (record.isSystemPriority()) {
                 if (DEBUG_KEY_EVENT) {
-                    Log.d(TAG, "Global priority session is updated, active=" + record.isActive());
+                    Log.d(
+                            TAG,
+                            "Global priority session updated - user id="
+                                    + record.getUserId()
+                                    + " package="
+                                    + record.getPackageName()
+                                    + " active="
+                                    + record.isActive());
                 }
                 user.pushAddressedPlayerChangedLocked();
             } else {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 839b699..61b6b24 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4566,6 +4566,7 @@
                 final Bundle extras = new Bundle();
                 extras.putInt(Intent.EXTRA_UID, pmi.getPackageUid(packageName, 0, userId));
                 extras.putInt(Intent.EXTRA_USER_HANDLE, userId);
+                extras.putLong(Intent.EXTRA_TIME, SystemClock.elapsedRealtime());
                 mHandler.post(() -> {
                     mBroadcastHelper.sendPackageBroadcast(Intent.ACTION_PACKAGE_UNSTOPPED,
                             packageName, extras,
@@ -6969,6 +6970,7 @@
             final Bundle extras = new Bundle();
             extras.putInt(Intent.EXTRA_UID, uid);
             extras.putInt(Intent.EXTRA_USER_HANDLE, userId);
+            extras.putLong(Intent.EXTRA_TIME, SystemClock.elapsedRealtime());
             mHandler.post(() -> {
                 mBroadcastHelper.sendPackageBroadcast(Intent.ACTION_PACKAGE_RESTARTED,
                         packageName, extras,
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 1d5cac5..7f55836 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -833,18 +833,24 @@
         private final SparseArray<Integer> mProcStatesCache = new SparseArray<>();
 
         public boolean isUidForeground(int uid) {
-            return mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
-                    <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+            synchronized (this) {
+                return mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
+                        <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+            }
         }
 
         @Override
         public void onUidGone(int uid, boolean disabled) {
-            mProcStatesCache.delete(uid);
+            synchronized (this) {
+                mProcStatesCache.delete(uid);
+            }
         }
 
         @Override
         public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
-            mProcStatesCache.put(uid, procState);
+            synchronized (this) {
+                mProcStatesCache.put(uid, procState);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/SnapshotController.java b/services/core/java/com/android/server/wm/SnapshotController.java
index 2e7ff7a..f68e392 100644
--- a/services/core/java/com/android/server/wm/SnapshotController.java
+++ b/services/core/java/com/android/server/wm/SnapshotController.java
@@ -85,7 +85,7 @@
             if (info.mWindowingMode == WINDOWING_MODE_PINNED) continue;
             if (info.mContainer.isActivityTypeHome()) continue;
             final Task task = info.mContainer.asTask();
-            if (task != null && !task.isVisibleRequested()) {
+            if (task != null && !task.mCreatedByOrganizer && !task.isVisibleRequested()) {
                 mTaskSnapshotController.recordSnapshot(task, info);
             }
             // Won't need to capture activity snapshot in close transition.
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index debd891..215934f 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -272,12 +272,12 @@
         <xs:element name="brightnessDecreaseDebounceMillis" type="xs:nonNegativeInteger">
             <xs:annotation name="final"/>
         </xs:element>
-        <!-- Animation time for brightness increase in millis -->
-        <xs:element  name="brightnessIncreaseDurationMillis" type="xs:nonNegativeInteger">
+        <!-- Animation speed for brightness increase. In framework brightness units per second. -->
+        <xs:element  name="screenBrightnessRampIncrease" type="nonNegativeDecimal">
             <xs:annotation name="final"/>
         </xs:element>
-        <!-- Animation time for brightness decrease in millis -->
-        <xs:element name="brightnessDecreaseDurationMillis" type="xs:nonNegativeInteger">
+        <!-- Animation speed for brightness decrease. In framework brightness units per second. -->
+        <xs:element name="screenBrightnessRampDecrease" type="nonNegativeDecimal">
             <xs:annotation name="final"/>
         </xs:element>
     </xs:complexType>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 2d27f0c..f7e0043 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -179,15 +179,15 @@
   public class HdrBrightnessConfig {
     ctor public HdrBrightnessConfig();
     method public final java.math.BigInteger getBrightnessDecreaseDebounceMillis();
-    method public final java.math.BigInteger getBrightnessDecreaseDurationMillis();
     method public final java.math.BigInteger getBrightnessIncreaseDebounceMillis();
-    method public final java.math.BigInteger getBrightnessIncreaseDurationMillis();
     method @NonNull public final com.android.server.display.config.NonNegativeFloatToFloatMap getBrightnessMap();
+    method public final java.math.BigDecimal getScreenBrightnessRampDecrease();
+    method public final java.math.BigDecimal getScreenBrightnessRampIncrease();
     method public final void setBrightnessDecreaseDebounceMillis(java.math.BigInteger);
-    method public final void setBrightnessDecreaseDurationMillis(java.math.BigInteger);
     method public final void setBrightnessIncreaseDebounceMillis(java.math.BigInteger);
-    method public final void setBrightnessIncreaseDurationMillis(java.math.BigInteger);
     method public final void setBrightnessMap(@NonNull com.android.server.display.config.NonNegativeFloatToFloatMap);
+    method public final void setScreenBrightnessRampDecrease(java.math.BigDecimal);
+    method public final void setScreenBrightnessRampIncrease(java.math.BigDecimal);
   }
 
   public class HighBrightnessMode {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 25e8475..922f69c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -40,6 +40,7 @@
 import android.app.admin.PolicyValue;
 import android.app.admin.TargetUser;
 import android.app.admin.UserRestrictionPolicyKey;
+import android.app.admin.flags.FlagUtils;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -65,7 +66,6 @@
 import com.android.internal.util.XmlUtils;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
-import com.android.server.devicepolicy.flags.FlagUtils;
 import com.android.server.utils.Slogf;
 
 import libcore.io.IoUtils;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 5a620a3..8509155 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -330,6 +330,7 @@
 import android.app.admin.UnsafeStateException;
 import android.app.admin.UserRestrictionPolicyKey;
 import android.app.admin.WifiSsidPolicy;
+import android.app.admin.flags.FlagUtils;
 import android.app.backup.IBackupManager;
 import android.app.compat.CompatChanges;
 import android.app.role.OnRoleHoldersChangedListener;
@@ -490,7 +491,6 @@
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
 import com.android.server.devicepolicy.ActiveAdmin.TrustAgentInfo;
-import com.android.server.devicepolicy.flags.FlagUtils;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.pdb.PersistentDataBlockManagerInternal;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/flags/Android.bp b/services/devicepolicy/java/com/android/server/devicepolicy/flags/Android.bp
deleted file mode 100644
index 1a45782..0000000
--- a/services/devicepolicy/java/com/android/server/devicepolicy/flags/Android.bp
+++ /dev/null
@@ -1,16 +0,0 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-aconfig_declarations {
-    name: "device_policy_aconfig_flags",
-    package: "com.android.server.devicepolicy.flags",
-    srcs: [
-        "flags.aconfig",
-    ],
-}
-
-java_aconfig_library {
-    name: "device_policy_aconfig_flags_lib",
-    aconfig_declarations: "device_policy_aconfig_flags",
-}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig b/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig
deleted file mode 100644
index 0dde496..0000000
--- a/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig
+++ /dev/null
@@ -1,14 +0,0 @@
-package: "com.android.server.devicepolicy.flags"
-
-flag {
-  name: "policy_engine_migration_v2_enabled"
-  namespace: "enterprise"
-  description: "V2 of the policy engine migrations for Android V"
-  bug: "289520697"
-}
-flag {
-  name: "device_policy_size_tracking_enabled"
-  namespace: "enterprise"
-  description: "Add feature to track the total policy size and have a max threshold."
-  bug: "281543351"
-}
\ No newline at end of file
diff --git a/services/midi/Android.bp b/services/midi/Android.bp
index 4b5f8a7..a385fe3 100644
--- a/services/midi/Android.bp
+++ b/services/midi/Android.bp
@@ -18,8 +18,8 @@
     name: "services.midi",
     defaults: ["platform_service_defaults"],
     srcs: [":services.midi-sources"],
-    libs: ["services.core"],
-    static_libs: [
+    libs: [
+        "services.core",
         "aconfig_midi_flags_java_lib",
     ],
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
index c12aedb..a400f12 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
@@ -90,7 +90,9 @@
                 .append("\n    isSlowChange:")
                 .append(displayBrightnessState.isSlowChange())
                 .append("\n    maxBrightness:")
-                .append(displayBrightnessState.getMaxBrightness());
+                .append(displayBrightnessState.getMaxBrightness())
+                .append("\n    customAnimationRate:")
+                .append(displayBrightnessState.getCustomAnimationRate());
         return sb.toString();
     }
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index c37d21a..179a9d5 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -570,9 +570,9 @@
         assertNotNull(data);
         assertEquals(2, data.mMaxBrightnessLimits.size());
         assertEquals(13000, data.mBrightnessDecreaseDebounceMillis);
-        assertEquals(10000, data.mBrightnessDecreaseDurationMillis);
+        assertEquals(0.1f, data.mScreenBrightnessRampDecrease, SMALL_DELTA);
         assertEquals(1000, data.mBrightnessIncreaseDebounceMillis);
-        assertEquals(11000, data.mBrightnessIncreaseDurationMillis);
+        assertEquals(0.11f, data.mScreenBrightnessRampIncrease, SMALL_DELTA);
 
         assertEquals(0.3f, data.mMaxBrightnessLimits.get(500f), SMALL_DELTA);
         assertEquals(0.6f, data.mMaxBrightnessLimits.get(1200f), SMALL_DELTA);
@@ -841,9 +841,9 @@
               + "        </point>\n"
               + "    </brightnessMap>\n"
               + "    <brightnessIncreaseDebounceMillis>1000</brightnessIncreaseDebounceMillis>\n"
-              + "    <brightnessIncreaseDurationMillis>11000</brightnessIncreaseDurationMillis>\n"
+              + "    <screenBrightnessRampIncrease>0.11</screenBrightnessRampIncrease>\n"
               + "    <brightnessDecreaseDebounceMillis>13000</brightnessDecreaseDebounceMillis>\n"
-              + "    <brightnessDecreaseDurationMillis>10000</brightnessDecreaseDurationMillis>\n"
+              + "    <screenBrightnessRampDecrease>0.1</screenBrightnessRampDecrease>\n"
               + "</hdrBrightnessConfig>";
     }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
index 8b54d6d2..47521d1 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -74,6 +74,7 @@
 import com.android.server.am.BatteryStatsService;
 import com.android.server.display.RampAnimator.DualRampAnimator;
 import com.android.server.display.brightness.BrightnessEvent;
+import com.android.server.display.brightness.clamper.BrightnessClamperController;
 import com.android.server.display.brightness.clamper.HdrClamper;
 import com.android.server.display.color.ColorDisplayService;
 import com.android.server.display.feature.DisplayManagerFlags;
@@ -1314,6 +1315,54 @@
     }
 
     @Test
+    public void testRampRateForClampersControllerApplied() {
+        float transitionRate = 1.5f;
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
+        when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
+        when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer(
+                invocation -> DisplayBrightnessState.builder()
+                        .setIsSlowChange(invocation.getArgument(2))
+                        .setBrightness(invocation.getArgument(1))
+                        .setMaxBrightness(PowerManager.BRIGHTNESS_MAX)
+                        .setCustomAnimationRate(transitionRate).build());
+
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.animator, atLeastOnce()).animateTo(anyFloat(), anyFloat(),
+                eq(transitionRate), anyBoolean());
+    }
+
+    @Test
+    public void testRampRateForClampersControllerNotApplied_ifDoze() {
+        float transitionRate = 1.5f;
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+        dpr.dozeScreenState = Display.STATE_UNKNOWN;
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
+        when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
+        when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer(
+                invocation -> DisplayBrightnessState.builder()
+                        .setIsSlowChange(invocation.getArgument(2))
+                        .setBrightness(invocation.getArgument(1))
+                        .setMaxBrightness(PowerManager.BRIGHTNESS_MAX)
+                        .setCustomAnimationRate(transitionRate).build());
+
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.animator, atLeastOnce()).animateTo(anyFloat(), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE), anyBoolean());
+        verify(mHolder.animator, never()).animateTo(anyFloat(), anyFloat(),
+                eq(transitionRate), anyBoolean());
+    }
+
+    @Test
     @RequiresFlagsDisabled(Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1)
     public void testRampMaxTimeInteractiveThenIdle() {
         // Send a display power request
@@ -1637,13 +1686,20 @@
                 mock(ScreenOffBrightnessSensorController.class);
         final HighBrightnessModeController hbmController = mock(HighBrightnessModeController.class);
         final HdrClamper hdrClamper = mock(HdrClamper.class);
+        BrightnessClamperController clamperController = mock(BrightnessClamperController.class);
 
         when(hbmController.getCurrentBrightnessMax()).thenReturn(PowerManager.BRIGHTNESS_MAX);
+        when(clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer(
+                invocation -> DisplayBrightnessState.builder()
+                        .setIsSlowChange(invocation.getArgument(2))
+                        .setBrightness(invocation.getArgument(1))
+                        .setMaxBrightness(PowerManager.BRIGHTNESS_MAX)
+                        .setCustomAnimationRate(-1).build());
 
         TestInjector injector = spy(new TestInjector(displayPowerState, animator,
                 automaticBrightnessController, wakelockController, brightnessMappingStrategy,
                 hysteresisLevels, screenOffBrightnessSensorController, hbmController, hdrClamper,
-                flags));
+                clamperController, flags));
 
         final LogicalDisplay display = mock(LogicalDisplay.class);
         final DisplayDevice device = mock(DisplayDevice.class);
@@ -1662,8 +1718,8 @@
 
         return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting,
                 animator, automaticBrightnessController, wakelockController,
-                screenOffBrightnessSensorController, hbmController, hdrClamper, hbmMetadata,
-                brightnessMappingStrategy, injector, config);
+                screenOffBrightnessSensorController, hbmController, hdrClamper, clamperController,
+                hbmMetadata, brightnessMappingStrategy, injector, config);
     }
 
     /**
@@ -1682,6 +1738,7 @@
         public final HighBrightnessModeController hbmController;
 
         public final HdrClamper hdrClamper;
+        public final BrightnessClamperController clamperController;
         public final HighBrightnessModeMetadata hbmMetadata;
         public final BrightnessMappingStrategy brightnessMappingStrategy;
         public final DisplayPowerController2.Injector injector;
@@ -1695,6 +1752,7 @@
                 ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
                 HighBrightnessModeController hbmController,
                 HdrClamper hdrClamper,
+                BrightnessClamperController clamperController,
                 HighBrightnessModeMetadata hbmMetadata,
                 BrightnessMappingStrategy brightnessMappingStrategy,
                 DisplayPowerController2.Injector injector,
@@ -1709,6 +1767,7 @@
             this.screenOffBrightnessSensorController = screenOffBrightnessSensorController;
             this.hbmController = hbmController;
             this.hdrClamper = hdrClamper;
+            this.clamperController = clamperController;
             this.hbmMetadata = hbmMetadata;
             this.brightnessMappingStrategy = brightnessMappingStrategy;
             this.injector = injector;
@@ -1728,6 +1787,8 @@
 
         private final HdrClamper mHdrClamper;
 
+        private final BrightnessClamperController mClamperController;
+
         private final DisplayManagerFlags mFlags;
 
         TestInjector(DisplayPowerState dps, DualRampAnimator<DisplayPowerState> animator,
@@ -1738,6 +1799,7 @@
                 ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
                 HighBrightnessModeController highBrightnessModeController,
                 HdrClamper hdrClamper,
+                BrightnessClamperController clamperController,
                 DisplayManagerFlags flags) {
             mDisplayPowerState = dps;
             mAnimator = animator;
@@ -1748,6 +1810,7 @@
             mScreenOffBrightnessSensorController = screenOffBrightnessSensorController;
             mHighBrightnessModeController = highBrightnessModeController;
             mHdrClamper = hdrClamper;
+            mClamperController = clamperController;
             mFlags = flags;
         }
 
@@ -1864,6 +1927,14 @@
         }
 
         @Override
+        BrightnessClamperController getBrightnessClamperController(Handler handler,
+                BrightnessClamperController.ClamperChangeListener clamperChangeListener,
+                BrightnessClamperController.DisplayDeviceData data, Context context,
+                DisplayManagerFlags flags) {
+            return mClamperController;
+        }
+
+        @Override
         DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
                 SensorManager sensorManager, Resources resources) {
             return mDisplayWhiteBalanceControllerMock;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
index c0e0df9..ff2b1f4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
@@ -137,8 +137,10 @@
         float initialBrightness = 0.8f;
         boolean initialSlowChange = true;
         float clampedBrightness = 0.6f;
+        float customAnimationRate = 0.01f;
         when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
         when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL);
+        when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
         when(mMockClamper.isActive()).thenReturn(false);
         mTestInjector.mCapturedChangeListener.onChanged();
         mTestHandler.flush();
@@ -150,6 +152,7 @@
         assertEquals(PowerManager.BRIGHTNESS_MAX, state.getMaxBrightness(), FLOAT_TOLERANCE);
         assertEquals(0,
                 state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
+        assertEquals(-1, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
         assertEquals(initialSlowChange, state.isSlowChange());
     }
 
@@ -158,8 +161,10 @@
         float initialBrightness = 0.8f;
         boolean initialSlowChange = true;
         float clampedBrightness = 0.6f;
+        float customAnimationRate = 0.01f;
         when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
         when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL);
+        when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
         when(mMockClamper.isActive()).thenReturn(true);
         mTestInjector.mCapturedChangeListener.onChanged();
         mTestHandler.flush();
@@ -171,6 +176,7 @@
         assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
         assertEquals(BrightnessReason.MODIFIER_THROTTLED,
                 state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
+        assertEquals(customAnimationRate, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
         assertFalse(state.isSlowChange());
     }
 
@@ -179,8 +185,10 @@
         float initialBrightness = 0.6f;
         boolean initialSlowChange = true;
         float clampedBrightness = 0.8f;
+        float customAnimationRate = 0.01f;
         when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
         when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL);
+        when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
         when(mMockClamper.isActive()).thenReturn(true);
         mTestInjector.mCapturedChangeListener.onChanged();
         mTestHandler.flush();
@@ -192,6 +200,7 @@
         assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
         assertEquals(BrightnessReason.MODIFIER_THROTTLED,
                 state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
+        assertEquals(customAnimationRate, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
         assertFalse(state.isSlowChange());
     }
 
@@ -200,8 +209,10 @@
         float initialBrightness = 0.8f;
         boolean initialSlowChange = true;
         float clampedBrightness = 0.6f;
+        float customAnimationRate = 0.01f;
         when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
         when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL);
+        when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
         when(mMockClamper.isActive()).thenReturn(true);
         mTestInjector.mCapturedChangeListener.onChanged();
         mTestHandler.flush();
@@ -216,6 +227,7 @@
         assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
         assertEquals(BrightnessReason.MODIFIER_THROTTLED,
                 state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
+        assertEquals(customAnimationRate, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
         assertEquals(initialSlowChange, state.isSlowChange());
     }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
index ee187ba..8d8274c 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
@@ -56,9 +56,9 @@
     private static final HdrBrightnessData TEST_HDR_DATA = new HdrBrightnessData(
             Map.of(500f, 0.6f),
             /* brightnessIncreaseDebounceMillis= */ 1000,
-            /* brightnessIncreaseDurationMillis= */ 2000,
+            /* screenBrightnessRampIncrease= */ 0.02f,
             /* brightnessDecreaseDebounceMillis= */ 3000,
-            /* brightnessDecreaseDurationMillis= */4000
+            /* screenBrightnessRampDecrease= */0.04f
     );
 
     private static final int WIDTH = 600;
@@ -152,8 +152,7 @@
         mClock.fastForward(3000);
         mTestHandler.timeAdvance();
         assertEquals(0.6f, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
-        // 0.6 to HLG = 0.905727, rate = (1-0.905727) / 4
-        assertEquals(0.023568f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
+        assertEquals(0.04, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
     }
 
     @Test
@@ -181,8 +180,7 @@
         mClock.fastForward(1000);
         mTestHandler.timeAdvance();
         assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
-        // 0.6 to HLG = 0.905727, rate = (1-0.905727) / 2
-        assertEquals(0.047137f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
+        assertEquals(0.02f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
     }
 
     @Test
@@ -209,8 +207,7 @@
         mClock.fastForward(3000);
         mTestHandler.timeAdvance();
         assertEquals(0.6f, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
-        // 0.6 to HLG = 0.905727, rate = (1-0.905727) / 4
-        assertEquals(0.023568f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
+        assertEquals(0.04f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
     }
 
     // MsgInfo.sendTime is calculated first by adding SystemClock.uptimeMillis()
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
index 121e296..337dd22 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
@@ -91,7 +91,6 @@
     private static final Multimap<Class<?>, String> KNOWN_BAD =
             ImmutableMultimap.<Class<?>, String>builder()
                     .put(Person.Builder.class, "setUri") // TODO: b/281044385
-                    .put(RemoteViews.class, "setRemoteAdapter") // TODO: b/281044385
                     .build();
 
     // Types that we can't really produce. No methods receiving these parameters will be invoked.
diff --git a/tests/FsVerityTest/AndroidTest.xml b/tests/FsVerityTest/AndroidTest.xml
index 49cbde0..d2537f6 100644
--- a/tests/FsVerityTest/AndroidTest.xml
+++ b/tests/FsVerityTest/AndroidTest.xml
@@ -24,7 +24,7 @@
     <!-- This test requires root to write against block device. -->
     <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
 
-    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="test-file-name" value="FsVerityTestApp.apk"/>
         <option name="cleanup-apks" value="true"/>
     </target_preparer>
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
index 01f1591..3d5a0f7 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
@@ -16,6 +16,7 @@
 
 package android.net.wifi.sharedconnectivity.app;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -169,6 +170,7 @@
          * @return Returns the Builder object.
          */
         @NonNull
+        @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status")
         public Builder setBatteryCharging(boolean isBatteryCharging) {
             mIsBatteryCharging = isBatteryCharging;
             return this;
@@ -283,6 +285,7 @@
      *
      * @return Returns true if the battery of the remote device is charging.
      */
+    @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status")
     public boolean isBatteryCharging() {
         return mIsBatteryCharging;
     }
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
index 71ac94b..b0f68f7 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
@@ -17,6 +17,7 @@
 package android.net.wifi.sharedconnectivity.app;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -296,6 +297,7 @@
      */
     @TestApi
     @NonNull
+    @FlaggedApi("com.android.wifi.flags.shared_connectivity_broadcast_receiver_test_api")
     public BroadcastReceiver getBroadcastReceiver() {
         return mBroadcastReceiver;
     }