Merge "Update FR bug numbers of windowing_sdk flags" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 0ee7ace..e6e835b 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -19,6 +19,7 @@
     ":android.app.flags-aconfig-java{.generated_srcjars}",
     ":android.app.smartspace.flags-aconfig-java{.generated_srcjars}",
     ":android.app.usage.flags-aconfig-java{.generated_srcjars}",
+    ":android.app.wearable.flags-aconfig-java{.generated_srcjars}",
     ":android.appwidget.flags-aconfig-java{.generated_srcjars}",
     ":android.chre.flags-aconfig-java{.generated_srcjars}",
     ":android.companion.flags-aconfig-java{.generated_srcjars}",
@@ -1123,3 +1124,16 @@
     ],
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
+
+// Wearable Sensing
+aconfig_declarations {
+    name: "android.app.wearable.flags-aconfig",
+    package: "android.app.wearable",
+    srcs: ["core/java/android/app/wearable/*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.app.wearable.flags-aconfig-java",
+    aconfig_declarations: "android.app.wearable.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/api/Android.bp b/api/Android.bp
index b3b18b6..ef64a89 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -115,6 +115,7 @@
         "framework-pdf",
         "framework-permission",
         "framework-permission-s",
+        "framework-profiling",
         "framework-scheduling",
         "framework-sdkextensions",
         "framework-statsd",
diff --git a/api/ApiDocs.bp b/api/ApiDocs.bp
index 7ae3224..7ee4319 100644
--- a/api/ApiDocs.bp
+++ b/api/ApiDocs.bp
@@ -67,6 +67,7 @@
         ":framework-ondevicepersonalization-sources",
         ":framework-permission-sources",
         ":framework-permission-s-sources",
+        ":framework-profiling-sources",
         ":framework-scheduling-sources",
         ":framework-sdkextensions-sources",
         ":framework-statsd-sources",
diff --git a/boot/Android.bp b/boot/Android.bp
index 228d060..cdfa7c80 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -122,6 +122,10 @@
             module: "com.android.permission-bootclasspath-fragment",
         },
         {
+            apex: "com.android.profiling",
+            module: "com.android.profiling-bootclasspath-fragment",
+        },
+        {
             apex: "com.android.scheduling",
             module: "com.android.scheduling-bootclasspath-fragment",
         },
diff --git a/core/api/current.txt b/core/api/current.txt
index e957676..b17e3343 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -13,6 +13,7 @@
     field public static final String ACCESS_CHECKIN_PROPERTIES = "android.permission.ACCESS_CHECKIN_PROPERTIES";
     field public static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
     field public static final String ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION";
+    field @FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles") public static final String ACCESS_HIDDEN_PROFILES = "android.permission.ACCESS_HIDDEN_PROFILES";
     field public static final String ACCESS_LOCATION_EXTRA_COMMANDS = "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS";
     field public static final String ACCESS_MEDIA_LOCATION = "android.permission.ACCESS_MEDIA_LOCATION";
     field public static final String ACCESS_NETWORK_STATE = "android.permission.ACCESS_NETWORK_STATE";
@@ -89,6 +90,7 @@
     field public static final String DELETE_PACKAGES = "android.permission.DELETE_PACKAGES";
     field public static final String DELIVER_COMPANION_MESSAGES = "android.permission.DELIVER_COMPANION_MESSAGES";
     field public static final String DETECT_SCREEN_CAPTURE = "android.permission.DETECT_SCREEN_CAPTURE";
+    field @FlaggedApi("com.android.window.flags.screen_recording_callbacks") public static final String DETECT_SCREEN_RECORDING = "android.permission.DETECT_SCREEN_RECORDING";
     field public static final String DIAGNOSTIC = "android.permission.DIAGNOSTIC";
     field public static final String DISABLE_KEYGUARD = "android.permission.DISABLE_KEYGUARD";
     field public static final String DUMP = "android.permission.DUMP";
@@ -53783,6 +53785,7 @@
     method public default void addCrossWindowBlurEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
     method public default void addCrossWindowBlurEnabledListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method public default void addProposedRotationListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
+    method @FlaggedApi("com.android.window.flags.screen_recording_callbacks") @RequiresPermission(android.Manifest.permission.DETECT_SCREEN_RECORDING) public default int addScreenRecordingCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @NonNull public default android.view.WindowMetrics getCurrentWindowMetrics();
     method @Deprecated public android.view.Display getDefaultDisplay();
     method @NonNull public default android.view.WindowMetrics getMaximumWindowMetrics();
@@ -53792,6 +53795,7 @@
     method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void registerUnbatchedSurfaceControlInputReceiver(int, @NonNull android.os.IBinder, @NonNull android.view.SurfaceControl, @NonNull android.os.Looper, @NonNull android.view.SurfaceControlInputReceiver);
     method public default void removeCrossWindowBlurEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
     method public default void removeProposedRotationListener(@NonNull java.util.function.IntConsumer);
+    method @FlaggedApi("com.android.window.flags.screen_recording_callbacks") @RequiresPermission(android.Manifest.permission.DETECT_SCREEN_RECORDING) public default void removeScreenRecordingCallback(@NonNull java.util.function.Consumer<java.lang.Integer>);
     method public void removeViewImmediate(android.view.View);
     method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void unregisterSurfaceControlInputReceiver(@NonNull android.view.SurfaceControl);
     method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void unregisterTrustedPresentationListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
@@ -53811,6 +53815,8 @@
     field public static final String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
     field public static final String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION = "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
     field @FlaggedApi("com.android.window.flags.supports_multi_instance_system_ui") public static final String PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI = "android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI";
+    field @FlaggedApi("com.android.window.flags.screen_recording_callbacks") public static final int SCREEN_RECORDING_STATE_NOT_VISIBLE = 0; // 0x0
+    field @FlaggedApi("com.android.window.flags.screen_recording_callbacks") public static final int SCREEN_RECORDING_STATE_VISIBLE = 1; // 0x1
   }
 
   public static class WindowManager.BadTokenException extends java.lang.RuntimeException {
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 55ed1f5..d331455 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -187,6 +187,7 @@
     method @NonNull public static android.media.BluetoothProfileConnectionInfo createA2dpInfo(boolean, int);
     method @NonNull public static android.media.BluetoothProfileConnectionInfo createA2dpSinkInfo(int);
     method @NonNull public static android.media.BluetoothProfileConnectionInfo createHearingAidInfo(boolean);
+    method @FlaggedApi("android.media.audio.sco_managed_by_audio") @NonNull public static android.media.BluetoothProfileConnectionInfo createHfpInfo();
     method @NonNull public static android.media.BluetoothProfileConnectionInfo createLeAudioInfo(boolean, boolean);
     method @NonNull public static android.media.BluetoothProfileConnectionInfo createLeAudioOutputInfo(boolean, int);
     method public int describeContents();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 6afc948..01f54ad 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -414,6 +414,7 @@
     field public static final String WRITE_OBB = "android.permission.WRITE_OBB";
     field public static final String WRITE_SECURITY_LOG = "android.permission.WRITE_SECURITY_LOG";
     field public static final String WRITE_SMS = "android.permission.WRITE_SMS";
+    field @FlaggedApi("android.provider.user_keys") public static final String WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS = "android.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS";
   }
 
   public static final class Manifest.permission_group {
@@ -3157,6 +3158,7 @@
     field public static final int STATUS_SUCCESS = 1; // 0x1
     field public static final int STATUS_UNKNOWN = 0; // 0x0
     field public static final int STATUS_UNSUPPORTED = 2; // 0x2
+    field @FlaggedApi("android.app.wearable.enable_unsupported_operation_status_code") public static final int STATUS_UNSUPPORTED_OPERATION = 6; // 0x6
     field public static final int STATUS_WEARABLE_UNAVAILABLE = 4; // 0x4
   }
 
@@ -11303,6 +11305,12 @@
     field public static final int ERROR_UNKNOWN = 0; // 0x0
   }
 
+  @FlaggedApi("android.provider.user_keys") public class ContactKeysManager {
+    method @RequiresPermission(allOf={android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS, android.Manifest.permission.WRITE_CONTACTS}) public boolean updateContactKeyLocalVerificationState(@NonNull String, @NonNull String, @NonNull String, @NonNull String, int);
+    method @RequiresPermission(allOf={android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS, android.Manifest.permission.WRITE_CONTACTS}) public boolean updateContactKeyRemoteVerificationState(@NonNull String, @NonNull String, @NonNull String, @NonNull String, int);
+    method @RequiresPermission(allOf={android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS, android.Manifest.permission.WRITE_CONTACTS}) public boolean updateSelfKeyRemoteVerificationState(@NonNull String, @NonNull String, @NonNull String, int);
+  }
+
   @Deprecated public static final class ContactsContract.MetadataSync implements android.provider.BaseColumns android.provider.ContactsContract.MetadataSyncColumns {
     field @Deprecated public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_metadata";
     field @Deprecated public static final String CONTENT_TYPE = "vnd.android.cursor.dir/contact_metadata";
@@ -13441,6 +13449,7 @@
 
   public abstract class Connection extends android.telecom.Conferenceable {
     method @Deprecated public final android.telecom.AudioState getAudioState();
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public final int getCallDirection();
     method @IntRange(from=0) public final long getConnectTimeMillis();
     method public final long getConnectionStartElapsedRealtimeMillis();
     method @Nullable public android.telecom.PhoneAccountHandle getPhoneAccountHandle();
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 6285eb3..084c71f 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -147,6 +147,7 @@
  * </p>
  */
 @SystemService(Context.ACTIVITY_SERVICE)
+@android.ravenwood.annotation.RavenwoodKeepPartialClass
 public class ActivityManager {
     private static String TAG = "ActivityManager";
 
@@ -966,6 +967,7 @@
      * Print capability bits in human-readable form.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static void printCapabilitiesSummary(PrintWriter pw, @ProcessCapability int caps) {
         pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0 ? 'L' : '-');
         pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0 ? 'C' : '-');
@@ -976,6 +978,7 @@
     }
 
     /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static void printCapabilitiesSummary(StringBuilder sb, @ProcessCapability int caps) {
         sb.append((caps & PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0 ? 'L' : '-');
         sb.append((caps & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0 ? 'C' : '-');
@@ -989,6 +992,7 @@
      * Print capability bits in human-readable form.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static void printCapabilitiesFull(PrintWriter pw, @ProcessCapability int caps) {
         printCapabilitiesSummary(pw, caps);
         final int remain = caps & ~PROCESS_CAPABILITY_ALL;
@@ -999,6 +1003,7 @@
     }
 
     /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static String getCapabilitiesSummary(@ProcessCapability int caps) {
         final StringBuilder sb = new StringBuilder();
         printCapabilitiesSummary(sb, caps);
@@ -1018,6 +1023,7 @@
      * @return the value of the corresponding enums.proto ProcessStateEnum value.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static final int processStateAmToProto(int amInt) {
         switch (amInt) {
             case PROCESS_STATE_UNKNOWN:
@@ -1078,16 +1084,19 @@
     public static final int MAX_PROCESS_STATE = PROCESS_STATE_NONEXISTENT;
 
     /** @hide Should this process state be considered a background state? */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static final boolean isProcStateBackground(int procState) {
         return procState >= PROCESS_STATE_TRANSIENT_BACKGROUND;
     }
 
     /** @hide Should this process state be considered in the cache? */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static final boolean isProcStateCached(int procState) {
         return procState >= PROCESS_STATE_CACHED_ACTIVITY;
     }
 
     /** @hide Is this a foreground service type? */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isForegroundService(int procState) {
         return procState == PROCESS_STATE_FOREGROUND_SERVICE;
     }
@@ -1161,10 +1170,25 @@
         mContext = context;
     }
 
+    private static volatile int sCurrentUser$ravenwood = UserHandle.USER_NULL;
+
+    /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
+    public static void init$ravenwood(int currentUser) {
+        sCurrentUser$ravenwood = currentUser;
+    }
+
+    /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
+    public static void reset$ravenwood() {
+        sCurrentUser$ravenwood = UserHandle.USER_NULL;
+    }
+
     /**
      * Returns whether the launch was successful.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static final boolean isStartResultSuccessful(int result) {
         return FIRST_START_SUCCESS_CODE <= result && result <= LAST_START_SUCCESS_CODE;
     }
@@ -1173,6 +1197,7 @@
      * Returns whether the launch result was a fatal error.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static final boolean isStartResultFatalError(int result) {
         return FIRST_START_FATAL_ERROR_CODE <= result && result <= LAST_START_FATAL_ERROR_CODE;
     }
@@ -1343,6 +1368,7 @@
     public @interface RestrictionLevel{}
 
     /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static String restrictionLevelToName(@RestrictionLevel int level) {
         switch (level) {
             case RESTRICTION_LEVEL_UNKNOWN:
@@ -4779,6 +4805,7 @@
      * Returns "true" if the user interface is currently being messed with
      * by a monkey.
      */
+    @android.ravenwood.annotation.RavenwoodReplace
     public static boolean isUserAMonkey() {
         try {
             return getService().isUserAMonkey();
@@ -4787,6 +4814,12 @@
         }
     }
 
+    /** @hide */
+    public static boolean isUserAMonkey$ravenwood() {
+        // Ravenwood environment is never considered a "monkey"
+        return false;
+    }
+
     /**
      * Returns "true" if device is running in a test harness.
      *
@@ -4973,6 +5006,7 @@
             "android.permission.INTERACT_ACROSS_USERS",
             "android.permission.INTERACT_ACROSS_USERS_FULL"
     })
+    @android.ravenwood.annotation.RavenwoodReplace
     public static int getCurrentUser() {
         try {
             return getService().getCurrentUserId();
@@ -4981,6 +5015,11 @@
         }
     }
 
+    /** @hide */
+    public static int getCurrentUser$ravenwood() {
+        return sCurrentUser$ravenwood;
+    }
+
     /**
      * @param userid the user's id. Zero indicates the default user.
      * @hide
@@ -5320,6 +5359,7 @@
     /**
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodReplace
     public static boolean isSystemReady() {
         if (!sSystemReady) {
             if (ActivityThread.isSystem()) {
@@ -5334,6 +5374,12 @@
         return sSystemReady;
     }
 
+    /** @hide */
+    public static boolean isSystemReady$ravenwood() {
+        // Ravenwood environment is always considered as booted and ready
+        return true;
+    }
+
     /**
      * @hide
      */
@@ -5661,11 +5707,13 @@
     }
 
     /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isProcStateConsideredInteraction(@ProcessState int procState) {
         return (procState <= PROCESS_STATE_TOP || procState == PROCESS_STATE_BOUND_TOP);
     }
 
     /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static String procStateToString(int procState) {
         final String procStateStr;
         switch (procState) {
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index d540748..e2e2f1d 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -225,7 +225,7 @@
             boolean focused, boolean newSessionId);
     boolean requestAutofillData(in IAssistDataReceiver receiver, in Bundle receiverExtras,
             in IBinder activityToken, int flags);
-    boolean isAssistDataAllowedOnCurrentActivity();
+    boolean isAssistDataAllowed();
     boolean requestAssistDataForTask(in IAssistDataReceiver receiver, int taskId,
             in String callingPackageName, String callingAttributionTag);
 
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 8b8576a..b5e5074 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -721,28 +721,28 @@
     /**
      * Returns whether the device is currently locked for the user.
      * <p>
-     * This returns the device locked state for the {@link Context}'s user. If this user is the
-     * current user, then the device is considered "locked" when the lock screen is showing (i.e.
-     * {@link #isKeyguardLocked()} returns {@code true}) and is not trivially dismissible (e.g. with
-     * swipe), and the user has a PIN, pattern, or password.
+     * This method returns the device locked state for the {@link Context}'s user. The device is
+     * considered to be locked for a user when the user's apps are currently inaccessible and some
+     * form of lock screen authentication is required to regain access to them. The lock screen
+     * authentication typically uses PIN, pattern, password, or biometric. Some devices may support
+     * additional methods, such as unlock using a paired smartwatch. "Swipe" does not count as
+     * authentication; if the lock screen is dismissible with swipe, for example due to the lock
+     * screen being set to Swipe or due to the device being kept unlocked by being near a trusted
+     * bluetooth device or in a trusted location, the device is considered unlocked.
+     * <div class="note">
      * <p>
-     * Note: the above definition implies that a user with no PIN, pattern, or password is never
-     * considered locked, even if the lock screen is showing and requesting a SIM card PIN. The
-     * device PIN and SIM PIN are separate. Also, the user is not considered locked if face
-     * authentication has just completed or a trust agent is keeping the device unlocked, since in
-     * these cases the lock screen is dismissible with swipe.
+     * <b>Note:</b> In the case of multiple full users, each user can have their own lock screen
+     * authentication configured. The device-locked state may differ between different users. For
+     * example, the device may be unlocked for the current user, but locked for a non-current user
+     * if lock screen authentication would be required to access that user's apps after switching to
+     * that user.
      * <p>
-     * For a user that is not the current user but can be switched to (usually this means "another
-     * full user"), and that has a PIN, pattern, or password, the device is always considered
-     * locked.
-     * <p>
-     * For a profile with a unified challenge, the device locked state is the same as that of the
-     * parent user.
-     * <p>
-     * For a profile with a separate challenge, the device becomes unlocked when the profile's PIN,
-     * pattern, password, or biometric is verified. It becomes locked when the parent user becomes
-     * locked, the screen turns off, the device reboots, the device policy controller locks the
-     * profile, or the timeout set by the device policy controller expires.
+     * In the case of a profile, when the device goes to the main lock screen, up to two layers of
+     * authentication may be required to regain access to the profile's apps: one to unlock the main
+     * lock screen, and one to unlock the profile (when a separate profile challenge is required).
+     * For a profile, the device is considered to be locked as long as any challenge remains, either
+     * the parent user's challenge (when applicable) or the profile's challenge (when applicable).
+     * </div>
      *
      * @return {@code true} if the device is currently locked for the user
      * @see #isKeyguardLocked()
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index d705eeb..8883907 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5946,6 +5946,12 @@
                 // there is enough space to do so (and fall back to the left edge if not).
                 big.setInt(R.id.actions, "setCollapsibleIndentDimen",
                         R.dimen.call_notification_collapsible_indent);
+                if (CallStyle.USE_NEW_ACTION_LAYOUT) {
+                    if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
+                        Log.d(TAG, "setting evenly divided mode on action list");
+                    }
+                    big.setBoolean(R.id.actions, "setEvenlyDividedMode", true);
+                }
             }
             big.setBoolean(R.id.actions, "setEmphasizedMode", emphasizedMode);
             if (numActions > 0 && !p.mHideActions) {
@@ -6421,7 +6427,15 @@
                     // Remove full-length color spans and ensure text contrast with the button fill.
                     title = ContrastColorUtil.ensureColorSpanContrast(title, buttonFillColor);
                 }
-                button.setTextViewText(R.id.action0, ensureColorSpanContrast(title, p));
+                final CharSequence label = ensureColorSpanContrast(title, p);
+                if (p.mCallStyleActions && CallStyle.USE_NEW_ACTION_LAYOUT) {
+                    if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
+                        Log.d(TAG, "new action layout enabled, gluing instead of setting text");
+                    }
+                    button.setCharSequence(R.id.action0, "glueLabel", label);
+                } else {
+                    button.setTextViewText(R.id.action0, label);
+                }
                 int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
                         buttonFillColor, mInNightMode);
                 if (tombstone) {
@@ -6438,7 +6452,14 @@
                 button.setColorStateList(R.id.action0, "setButtonBackground",
                         ColorStateList.valueOf(buttonFillColor));
                 if (p.mCallStyleActions) {
-                    button.setImageViewIcon(R.id.action0, action.getIcon());
+                    if (CallStyle.USE_NEW_ACTION_LAYOUT) {
+                        if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
+                            Log.d(TAG, "new action layout enabled, gluing instead of setting icon");
+                        }
+                        button.setIcon(R.id.action0, "glueIcon", action.getIcon());
+                    } else {
+                        button.setImageViewIcon(R.id.action0, action.getIcon());
+                    }
                     boolean priority = action.getExtras().getBoolean(CallStyle.KEY_ACTION_PRIORITY);
                     button.setBoolean(R.id.action0, "setIsPriority", priority);
                     int minWidthDimen =
@@ -9565,6 +9586,15 @@
      * </pre>
      */
     public static class CallStyle extends Style {
+        /**
+         * @hide
+         */
+        public static final boolean USE_NEW_ACTION_LAYOUT = false;
+
+        /**
+         * @hide
+         */
+        public static final boolean DEBUG_NEW_ACTION_LAYOUT = true;
 
         /**
          * @hide
diff --git a/core/java/android/app/QueuedWork.java b/core/java/android/app/QueuedWork.java
index edf0a46..6a114f9 100644
--- a/core/java/android/app/QueuedWork.java
+++ b/core/java/android/app/QueuedWork.java
@@ -114,6 +114,22 @@
     }
 
     /**
+     * Remove all Messages from the Handler with the given code.
+     *
+     * This method intentionally avoids creating the Handler if it doesn't
+     * already exist.
+     */
+    private static void handlerRemoveMessages(int what) {
+        synchronized (sLock) {
+            if (sHandler == null) {
+                // Nothing to remove
+                return;
+            }
+            getHandler().removeMessages(what);
+        }
+    }
+
+    /**
      * Add a finisher-runnable to wait for {@link #queue asynchronously processed work}.
      *
      * Used by SharedPreferences$Editor#startCommit().
@@ -156,17 +172,13 @@
         long startTime = System.currentTimeMillis();
         boolean hadMessages = false;
 
-        Handler handler = getHandler();
-
         synchronized (sLock) {
-            if (handler.hasMessages(QueuedWorkHandler.MSG_RUN)) {
-                // Delayed work will be processed at processPendingWork() below
-                handler.removeMessages(QueuedWorkHandler.MSG_RUN);
-
-                if (DEBUG) {
-                    hadMessages = true;
-                    Log.d(LOG_TAG, "waiting");
-                }
+            if (DEBUG) {
+                hadMessages = getHandler().hasMessages(QueuedWorkHandler.MSG_RUN);
+            }
+            handlerRemoveMessages(QueuedWorkHandler.MSG_RUN);
+            if (DEBUG && hadMessages) {
+                Log.d(LOG_TAG, "waiting");
             }
 
             // We should not delay any work as this might delay the finishers
@@ -257,7 +269,7 @@
                 sWork = new LinkedList<>();
 
                 // Remove all msg-s as all work will be processed now
-                getHandler().removeMessages(QueuedWorkHandler.MSG_RUN);
+                handlerRemoveMessages(QueuedWorkHandler.MSG_RUN);
             }
 
             if (work.size() > 0) {
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index a271328..a045eae 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -16,6 +16,8 @@
 
 package android.app;
 
+import static android.app.Flags.enableNightModeCache;
+
 import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
@@ -31,6 +33,7 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.os.Binder;
+import android.os.IpcDataCache;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.ServiceManager.ServiceNotFoundException;
@@ -874,6 +877,51 @@
         }
     }
 
+    private Integer getNightModeFromServer() {
+        try {
+            if (sGlobals != null) {
+                return sGlobals.mService.getNightMode();
+            }
+            return -1;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+
+    /**
+     * Retrieve the night mode for the user.
+     */
+    private final IpcDataCache.QueryHandler<Void, Integer> mNightModeQuery =
+            new IpcDataCache.QueryHandler<>() {
+
+                @Override
+                @NonNull
+                public Integer apply(Void query) {
+                    return getNightModeFromServer();
+                }
+            };
+
+    private static final String NIGHT_MODE_API = "getNightMode";
+
+    /**
+     * Cache the night mode for a user.
+     */
+    private final IpcDataCache<Void, Integer> mNightModeCache =
+            new IpcDataCache<>(1, IpcDataCache.MODULE_SYSTEM,
+                    NIGHT_MODE_API, /* cacheName= */ "NightModeCache", mNightModeQuery);
+
+    /**
+     * Invalidate the night mode cache.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NIGHT_MODE_CACHE)
+    public static void invalidateNightModeCache() {
+        IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM,
+                NIGHT_MODE_API);
+    }
+
     /**
      * Returns the currently configured night mode.
      * <p>
@@ -890,14 +938,11 @@
      * @see #setNightMode(int)
      */
     public @NightMode int getNightMode() {
-        if (sGlobals != null) {
-            try {
-                return sGlobals.mService.getNightMode();
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
+        if (enableNightModeCache()) {
+            return mNightModeCache.query(null);
+        } else {
+            return getNightModeFromServer();
         }
-        return -1;
     }
 
     /**
diff --git a/core/java/android/app/ui_mode_manager.aconfig b/core/java/android/app/ui_mode_manager.aconfig
new file mode 100644
index 0000000..1ae5264
--- /dev/null
+++ b/core/java/android/app/ui_mode_manager.aconfig
@@ -0,0 +1,8 @@
+package: "android.app"
+
+flag {
+     namespace: "system_performance"
+     name: "enable_night_mode_cache"
+     description: "Enables the use of binder caching for system night mode."
+     bug: "255999432"
+}
\ No newline at end of file
diff --git a/core/java/android/app/wearable/WearableSensingManager.java b/core/java/android/app/wearable/WearableSensingManager.java
index f1ca086..eca0039 100644
--- a/core/java/android/app/wearable/WearableSensingManager.java
+++ b/core/java/android/app/wearable/WearableSensingManager.java
@@ -18,6 +18,7 @@
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -99,6 +100,13 @@
      */
     public static final int STATUS_ACCESS_DENIED = 5;
 
+    /**
+     * The value of the status code that indicates the method called is not supported by the
+     * implementation of {@link WearableSensingService}.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_UNSUPPORTED_OPERATION_STATUS_CODE)
+    public static final int STATUS_UNSUPPORTED_OPERATION = 6;
+
     /** @hide */
     @IntDef(prefix = { "STATUS_" }, value = {
             STATUS_UNKNOWN,
@@ -106,7 +114,8 @@
             STATUS_UNSUPPORTED,
             STATUS_SERVICE_UNAVAILABLE,
             STATUS_WEARABLE_UNAVAILABLE,
-            STATUS_ACCESS_DENIED
+            STATUS_ACCESS_DENIED,
+            STATUS_UNSUPPORTED_OPERATION
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface StatusCode {}
diff --git a/core/java/android/app/wearable/flags.aconfig b/core/java/android/app/wearable/flags.aconfig
new file mode 100644
index 0000000..074ce9b
--- /dev/null
+++ b/core/java/android/app/wearable/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.app.wearable"
+
+flag {
+    name: "enable_unsupported_operation_status_code"
+    namespace: "machine_learning"
+    description: "This flag enables the WearableSensingManager#STATUS_UNSUPPORTED_OPERATION status code API."
+    bug: "301427767"
+}
\ No newline at end of file
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 629658b..19bce0b 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -191,3 +191,11 @@
     bug: "287487923"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "force_multi_arch_native_libs_match"
+    namespace: "package_manager_service"
+    description: "Feature flag to force an multiArch app's native libraries to match with the natively supported ABIs of the device"
+    bug: "282783453"
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index efb8607..d7e64b6 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -108,3 +108,17 @@
     bug: "316362775"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "enable_permission_to_access_hidden_profiles"
+    namespace: "profile_experiences"
+    description: "Add permission to access API hidden users data via system APIs"
+    bug: "321988638"
+}
+
+flag {
+    name: "handle_interleaved_settings_for_private_space"
+    namespace: "profile_experiences"
+    description: "Handle listing of private space apps in settings pages with interleaved content"
+    bug: "323212460"
+}
diff --git a/core/java/android/ddm/DdmHandleViewDebug.java b/core/java/android/ddm/DdmHandleViewDebug.java
index 0f66fcb..5cbf24f 100644
--- a/core/java/android/ddm/DdmHandleViewDebug.java
+++ b/core/java/android/ddm/DdmHandleViewDebug.java
@@ -16,16 +16,12 @@
 
 package android.ddm;
 
-import static com.android.internal.util.Preconditions.checkArgument;
-
 import android.util.Log;
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.ViewRootImpl;
 import android.view.WindowManagerGlobal;
 
-import com.android.internal.annotations.VisibleForTesting;
-
 import org.apache.harmony.dalvik.ddmc.Chunk;
 import org.apache.harmony.dalvik.ddmc.ChunkHandler;
 import org.apache.harmony.dalvik.ddmc.DdmServer;
@@ -35,10 +31,8 @@
 import java.io.DataOutputStream;
 import java.io.IOException;
 import java.io.OutputStreamWriter;
-import java.lang.reflect.Method;
 import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
 
 /**
  * Handle various requests related to profiling / debugging of the view system.
@@ -352,48 +346,17 @@
      *
      * The return value is encoded the same way as a single parameter (type + value)
      */
-    private Chunk invokeViewMethod(final View rootView, final View targetView, ByteBuffer in) {
+    private Chunk invokeViewMethod(View rootView, final View targetView, ByteBuffer in) {
         int l = in.getInt();
         String methodName = getString(in, l);
 
-        Class<?>[] argTypes;
-        Object[] args;
-        if (!in.hasRemaining()) {
-            argTypes = new Class<?>[0];
-            args = new Object[0];
-        } else {
-            int nArgs = in.getInt();
-            argTypes = new Class<?>[nArgs];
-            args = new Object[nArgs];
-
-            try {
-                deserializeMethodParameters(args, argTypes, in);
-            } catch (ViewMethodInvocationSerializationException e) {
-                return createFailChunk(ERR_INVALID_PARAM, e.getMessage());
-            }
-        }
-
-        Method method;
         try {
-            method = targetView.getClass().getMethod(methodName, argTypes);
-        } catch (NoSuchMethodException e) {
-            Log.e(TAG, "No such method: " + e.getMessage());
-            return createFailChunk(ERR_INVALID_PARAM,
-                    "No such method: " + e.getMessage());
-        }
-
-        try {
-            Object result = ViewDebug.invokeViewMethod(targetView, method, args);
-            Class<?> returnType = method.getReturnType();
-            byte[] returnValue = serializeReturnValue(returnType, returnType.cast(result));
+            byte[] returnValue =  ViewDebug.invokeViewMethod(targetView, methodName, in);
             return new Chunk(CHUNK_VUOP, returnValue, 0, returnValue.length);
+        } catch (ViewDebug.ViewMethodInvocationSerializationException e) {
+            return createFailChunk(ERR_INVALID_PARAM, e.getMessage());
         } catch (Exception e) {
-            Log.e(TAG, "Exception while invoking method: " + e.getCause().getMessage());
-            String msg = e.getCause().getMessage();
-            if (msg == null) {
-                msg = e.getCause().toString();
-            }
-            return createFailChunk(ERR_EXCEPTION, msg);
+            return createFailChunk(ERR_EXCEPTION, e.getMessage());
         }
     }
 
@@ -431,175 +394,4 @@
         byte[] data = b.toByteArray();
         return new Chunk(CHUNK_VUOP, data, 0, data.length);
     }
-
-    /**
-     * Deserializes parameters according to the VUOP_INVOKE_VIEW_METHOD protocol the {@code in}
-     * buffer.
-     *
-     * The length of {@code args} determines how many arguments are read. The {@code argTypes} must
-     * be the same length, and will be set to the argument types of the data read.
-     *
-     * @hide
-     */
-    @VisibleForTesting
-    public static void deserializeMethodParameters(
-            Object[] args, Class<?>[] argTypes, ByteBuffer in) throws
-            ViewMethodInvocationSerializationException {
-        checkArgument(args.length == argTypes.length);
-
-        for (int i = 0; i < args.length; i++) {
-            char typeSignature = in.getChar();
-            boolean isArray = typeSignature == SIG_ARRAY;
-            if (isArray) {
-                char arrayType = in.getChar();
-                if (arrayType != SIG_BYTE) {
-                    // This implementation only supports byte-arrays for now.
-                    throw new ViewMethodInvocationSerializationException(
-                            "Unsupported array parameter type (" + typeSignature
-                                    + ") to invoke view method @argument " + i);
-                }
-
-                int arrayLength = in.getInt();
-                if (arrayLength > in.remaining()) {
-                    // The sender did not actually sent the specified amount of bytes. This
-                    // avoids a malformed packet to trigger an out-of-memory error.
-                    throw new BufferUnderflowException();
-                }
-
-                byte[] byteArray = new byte[arrayLength];
-                in.get(byteArray);
-
-                argTypes[i] = byte[].class;
-                args[i] = byteArray;
-            } else {
-                switch (typeSignature) {
-                    case SIG_BOOLEAN:
-                        argTypes[i] = boolean.class;
-                        args[i] = in.get() != 0;
-                        break;
-                    case SIG_BYTE:
-                        argTypes[i] = byte.class;
-                        args[i] = in.get();
-                        break;
-                    case SIG_CHAR:
-                        argTypes[i] = char.class;
-                        args[i] = in.getChar();
-                        break;
-                    case SIG_SHORT:
-                        argTypes[i] = short.class;
-                        args[i] = in.getShort();
-                        break;
-                    case SIG_INT:
-                        argTypes[i] = int.class;
-                        args[i] = in.getInt();
-                        break;
-                    case SIG_LONG:
-                        argTypes[i] = long.class;
-                        args[i] = in.getLong();
-                        break;
-                    case SIG_FLOAT:
-                        argTypes[i] = float.class;
-                        args[i] = in.getFloat();
-                        break;
-                    case SIG_DOUBLE:
-                        argTypes[i] = double.class;
-                        args[i] = in.getDouble();
-                        break;
-                    case SIG_STRING: {
-                        argTypes[i] = String.class;
-                        int stringUtf8ByteCount = Short.toUnsignedInt(in.getShort());
-                        byte[] rawStringBuffer = new byte[stringUtf8ByteCount];
-                        in.get(rawStringBuffer);
-                        args[i] = new String(rawStringBuffer, StandardCharsets.UTF_8);
-                        break;
-                    }
-                    default:
-                        Log.e(TAG, "arg " + i + ", unrecognized type: " + typeSignature);
-                        throw new ViewMethodInvocationSerializationException(
-                                "Unsupported parameter type (" + typeSignature
-                                        + ") to invoke view method.");
-                }
-            }
-
-        }
-    }
-
-    /**
-     * Serializes {@code value} to the wire protocol of VUOP_INVOKE_VIEW_METHOD.
-     * @hide
-     */
-    @VisibleForTesting
-    public static byte[] serializeReturnValue(Class<?> type, Object value)
-            throws ViewMethodInvocationSerializationException, IOException {
-        ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream(1024);
-        DataOutputStream dos = new DataOutputStream(byteOutStream);
-
-        if (type.isArray()) {
-            if (!type.equals(byte[].class)) {
-                // Only byte arrays are supported currently.
-                throw new ViewMethodInvocationSerializationException(
-                        "Unsupported array return type (" + type + ")");
-            }
-            byte[] byteArray = (byte[]) value;
-            dos.writeChar(SIG_ARRAY);
-            dos.writeChar(SIG_BYTE);
-            dos.writeInt(byteArray.length);
-            dos.write(byteArray);
-        } else if (boolean.class.equals(type)) {
-            dos.writeChar(SIG_BOOLEAN);
-            dos.write((boolean) value ? 1 : 0);
-        } else if (byte.class.equals(type)) {
-            dos.writeChar(SIG_BYTE);
-            dos.writeByte((byte) value);
-        } else if (char.class.equals(type)) {
-            dos.writeChar(SIG_CHAR);
-            dos.writeChar((char) value);
-        } else if (short.class.equals(type)) {
-            dos.writeChar(SIG_SHORT);
-            dos.writeShort((short) value);
-        } else if (int.class.equals(type)) {
-            dos.writeChar(SIG_INT);
-            dos.writeInt((int) value);
-        } else if (long.class.equals(type)) {
-            dos.writeChar(SIG_LONG);
-            dos.writeLong((long) value);
-        } else if (double.class.equals(type)) {
-            dos.writeChar(SIG_DOUBLE);
-            dos.writeDouble((double) value);
-        } else if (float.class.equals(type)) {
-            dos.writeChar(SIG_FLOAT);
-            dos.writeFloat((float) value);
-        } else if (String.class.equals(type)) {
-            dos.writeChar(SIG_STRING);
-            dos.writeUTF(value != null ? (String) value : "");
-        } else {
-            dos.writeChar(SIG_VOID);
-        }
-
-        return byteOutStream.toByteArray();
-    }
-
-    // Prefixes for simple primitives. These match the JNI definitions.
-    private static final char SIG_ARRAY = '[';
-    private static final char SIG_BOOLEAN = 'Z';
-    private static final char SIG_BYTE = 'B';
-    private static final char SIG_SHORT = 'S';
-    private static final char SIG_CHAR = 'C';
-    private static final char SIG_INT = 'I';
-    private static final char SIG_LONG = 'J';
-    private static final char SIG_FLOAT = 'F';
-    private static final char SIG_DOUBLE = 'D';
-    private static final char SIG_VOID = 'V';
-    // Prefixes for some commonly used objects
-    private static final char SIG_STRING = 'R';
-
-    /**
-     * @hide
-     */
-    @VisibleForTesting
-    public static class ViewMethodInvocationSerializationException extends Exception {
-        ViewMethodInvocationSerializationException(String message) {
-            super(message);
-        }
-    }
 }
diff --git a/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl b/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl
index d51e62e..1488cff 100644
--- a/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl
+++ b/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl
@@ -15,6 +15,8 @@
  */
 package android.hardware.biometrics;
 
+import android.hardware.biometrics.BiometricSourceType;
+
 /**
  * Low-level callback interface between <Biometric>Manager and <Auth>Service. Allows core system
  * services (e.g. SystemUI) to register a listener for updates about the current state of biometric
@@ -49,4 +51,15 @@
      * @param userId The user Id for the requested authentication
      */
     void onAuthenticationFailed(int requestReason, int userId);
+
+    /**
+     * Defines behavior in response to biometric being acquired.
+     * @param biometricSourceType identifies [BiometricSourceType] biometric was acquired for
+     * @param requestReason reason from [BiometricRequestConstants.RequestReason] for authentication
+     * @param acquiredInfo [BiometricFingerprintConstants.FingerprintAcquired] int corresponding to
+     *                     a known acquired message.
+     */
+    void onAuthenticationAcquired(
+        in BiometricSourceType biometricSourceType, int requestReason, int acquiredInfo
+    );
 }
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 665d8d2..47f5b4c 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -5313,7 +5313,7 @@
      * </code></pre>
      * <ul>
      * <li>VIDEO_STABILIZATION_MODES: {OFF, PREVIEW}</li>
-     * <li>AE_TARGET_FPS_RANGE: {{<em>, 30}, {</em>, 60}}</li>
+     * <li>AE_TARGET_FPS_RANGE: &lbrace;{<em>, 30}, {</em>, 60}&rbrace;</li>
      * <li>DYNAMIC_RANGE_PROFILE: {STANDARD, HLG10}</li>
      * </ul>
      * <p>This key is available on all devices.</p>
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index 5078dc35..46705a3 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -29,6 +29,7 @@
 /**
  * Encapsulates a collection of attributes describing information about a vibration.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class VibrationAttributes implements Parcelable {
     private static final String TAG = "VibrationAttributes";
 
@@ -463,6 +464,7 @@
      * Builder class for {@link VibrationAttributes} objects.
      * By default, all information is set to UNKNOWN.
      */
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public static final class Builder {
         private int mUsage = USAGE_UNKNOWN;
         private int mOriginalAudioUsage = AudioAttributes.USAGE_UNKNOWN;
diff --git a/core/java/android/provider/ContactKeysManager.java b/core/java/android/provider/ContactKeysManager.java
index ecde699..bef6456 100644
--- a/core/java/android/provider/ContactKeysManager.java
+++ b/core/java/android/provider/ContactKeysManager.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -247,6 +248,44 @@
     }
 
     /**
+     * Updates a contact key entry's local verification state that belongs to the app identified
+     * by ownerPackageName.
+     *
+     * @param lookupKey the value that references the contact
+     * @param deviceId an app-specified identifier for the device
+     * @param accountId an app-specified identifier for the account
+     * @param ownerPackageName the package name of the app that owns the key
+     * @param localVerificationState the new local verification state
+     *
+     * @return true if the entry was updated, false otherwise.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS,
+            android.Manifest.permission.WRITE_CONTACTS})
+    public boolean updateContactKeyLocalVerificationState(@NonNull String lookupKey,
+            @NonNull String deviceId,
+            @NonNull String accountId,
+            @NonNull String ownerPackageName,
+            @VerificationState int localVerificationState) {
+        validateVerificationState(localVerificationState);
+
+        final Bundle extras = new Bundle();
+        extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+        extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+        extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+        extras.putString(ContactKeys.OWNER_PACKAGE_NAME, Objects.requireNonNull(ownerPackageName));
+        extras.putInt(ContactKeys.LOCAL_VERIFICATION_STATE, localVerificationState);
+
+        final Bundle response = nullSafeCall(mContentResolver,
+                ContactKeys.UPDATE_CONTACT_KEY_LOCAL_VERIFICATION_STATE_METHOD, extras);
+
+        return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+    }
+
+    /**
      * Updates a contact key entry's remote verification state that belongs to the caller app.
      *
      * @param lookupKey the value that references the contact
@@ -275,6 +314,45 @@
         return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
     }
 
+    /**
+     * Updates a contact key entry's remote verification state that belongs to the app identified
+     * by ownerPackageName.
+     *
+     * @param lookupKey the value that references the contact
+     * @param deviceId an app-specified identifier for the device
+     * @param accountId an app-specified identifier for the account
+     * @param ownerPackageName the package name of the app that owns the key
+     * @param remoteVerificationState the new remote verification state
+     *
+     * @return true if the entry was updated, false otherwise.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS,
+            android.Manifest.permission.WRITE_CONTACTS})
+    public boolean updateContactKeyRemoteVerificationState(@NonNull String lookupKey,
+            @NonNull String deviceId,
+            @NonNull String accountId,
+            @NonNull String ownerPackageName,
+            @VerificationState int remoteVerificationState) {
+        validateVerificationState(remoteVerificationState);
+
+        final Bundle extras = new Bundle();
+        extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+        extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+        extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+        extras.putString(ContactKeys.OWNER_PACKAGE_NAME, Objects.requireNonNull(ownerPackageName));
+        extras.putInt(ContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState);
+
+        final Bundle response = nullSafeCall(mContentResolver,
+                ContactKeys.UPDATE_CONTACT_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras);
+
+        return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+    }
+
+
     private static void validateVerificationState(int verificationState) {
         if (verificationState != UNVERIFIED
                 && verificationState != VERIFICATION_FAILED
@@ -297,12 +375,12 @@
     public boolean removeContactKey(@NonNull String lookupKey,
             @NonNull String deviceId,
             @NonNull String accountId) {
-        Bundle extras = new Bundle();
+        final Bundle extras = new Bundle();
         extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
         extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
         extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
 
-        Bundle response = nullSafeCall(mContentResolver,
+        final Bundle response = nullSafeCall(mContentResolver,
                 ContactKeys.REMOVE_CONTACT_KEY_METHOD, extras);
 
         return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
@@ -369,6 +447,41 @@
     }
 
     /**
+     * Updates a self key entry's remote verification state that belongs to the app identified
+     * by ownerPackageName.
+     *
+     * @param deviceId an app-specified identifier for the device
+     * @param accountId an app-specified identifier for the account
+     * @param ownerPackageName the package name of the app that owns the key
+     * @param remoteVerificationState the new remote verification state
+     *
+     * @return true if the entry was updated, false otherwise.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS,
+            android.Manifest.permission.WRITE_CONTACTS})
+    public boolean updateSelfKeyRemoteVerificationState(@NonNull String deviceId,
+            @NonNull String accountId,
+            @NonNull String ownerPackageName,
+            @VerificationState int remoteVerificationState) {
+        validateVerificationState(remoteVerificationState);
+
+        Bundle extras = new Bundle();
+        extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+        extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+        extras.putString(ContactKeys.OWNER_PACKAGE_NAME, Objects.requireNonNull(ownerPackageName));
+        extras.putInt(ContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState);
+
+        Bundle response = nullSafeCall(mContentResolver,
+                ContactKeys.UPDATE_SELF_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras);
+
+        return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+    }
+
+    /**
      * Maximum size of a contact key.
      */
     public static int getMaxKeySizeBytes() {
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 7903050..99863d0 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -1085,7 +1085,9 @@
 
     void unregisterTrustedPresentationListener(in ITrustedPresentationListener listener, int id);
 
+    @EnforcePermission("DETECT_SCREEN_RECORDING")
     boolean registerScreenRecordingCallback(IScreenRecordingCallback callback);
 
+    @EnforcePermission("DETECT_SCREEN_RECORDING")
     void unregisterScreenRecordingCallback(IScreenRecordingCallback callback);
 }
diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java
index 59ec605..9db1060 100644
--- a/core/java/android/view/InputWindowHandle.java
+++ b/core/java/android/view/InputWindowHandle.java
@@ -162,6 +162,12 @@
     public float alpha;
 
     /**
+     * Sets a property on this window indicating that its visible region should be considered when
+     * computing TrustedPresentation Thresholds.
+     */
+    public boolean canOccludePresentation;
+
+    /**
      * The input token for the window to which focus should be transferred when this input window
      * can be successfully focused. If null, this input window will not transfer its focus to
      * any other window.
@@ -205,6 +211,7 @@
         focusTransferTarget = other.focusTransferTarget;
         contentSize = new Size(other.contentSize.getWidth(), other.contentSize.getHeight());
         alpha = other.alpha;
+        canOccludePresentation = other.canOccludePresentation;
     }
 
     @Override
@@ -219,6 +226,7 @@
                 .append(", isClone=").append((inputConfig & InputConfig.CLONE) != 0)
                 .append(", contentSize=").append(contentSize)
                 .append(", alpha=").append(alpha)
+                .append(", canOccludePresentation=").append(canOccludePresentation)
                 .toString();
 
     }
diff --git a/core/java/android/view/ScreenRecordingCallbacks.java b/core/java/android/view/ScreenRecordingCallbacks.java
new file mode 100644
index 0000000..ee55737
--- /dev/null
+++ b/core/java/android/view/ScreenRecordingCallbacks.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2024 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.view;
+
+import static android.Manifest.permission.DETECT_SCREEN_RECORDING;
+import static android.view.WindowManager.SCREEN_RECORDING_STATE_NOT_VISIBLE;
+import static android.view.WindowManager.SCREEN_RECORDING_STATE_VISIBLE;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.view.WindowManager.ScreenRecordingState;
+import android.window.IScreenRecordingCallback;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * This class is responsible for calling app-registered screen recording callbacks. This class
+ * registers a single screen recording callback with WindowManagerService and calls the
+ * app-registered callbacks whenever that WindowManagerService callback is called.
+ *
+ * @hide
+ */
+public final class ScreenRecordingCallbacks {
+
+    private static ScreenRecordingCallbacks sInstance;
+    private static final Object sLock = new Object();
+
+    private final ArrayMap<Consumer<@ScreenRecordingState Integer>, Executor> mCallbacks =
+            new ArrayMap<>();
+
+    private IScreenRecordingCallback mCallbackNotifier;
+    private @ScreenRecordingState int mState = SCREEN_RECORDING_STATE_NOT_VISIBLE;
+
+    private ScreenRecordingCallbacks() {}
+
+    private static @NonNull IWindowManager getWindowManagerService() {
+        return Objects.requireNonNull(WindowManagerGlobal.getWindowManagerService());
+    }
+
+    static ScreenRecordingCallbacks getInstance() {
+        synchronized (sLock) {
+            if (sInstance == null) {
+                sInstance = new ScreenRecordingCallbacks();
+            }
+            return sInstance;
+        }
+    }
+
+    @RequiresPermission(DETECT_SCREEN_RECORDING)
+    @ScreenRecordingState
+    int addCallback(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<@ScreenRecordingState Integer> callback) {
+        synchronized (sLock) {
+            if (mCallbackNotifier == null) {
+                mCallbackNotifier =
+                        new IScreenRecordingCallback.Stub() {
+                            @Override
+                            public void onScreenRecordingStateChanged(
+                                    boolean visibleInScreenRecording) {
+                                int state =
+                                        visibleInScreenRecording
+                                                ? SCREEN_RECORDING_STATE_VISIBLE
+                                                : SCREEN_RECORDING_STATE_NOT_VISIBLE;
+                                notifyCallbacks(state);
+                            }
+                        };
+                try {
+                    boolean visibleInScreenRecording =
+                            getWindowManagerService()
+                                    .registerScreenRecordingCallback(mCallbackNotifier);
+                    mState =
+                            visibleInScreenRecording
+                                    ? SCREEN_RECORDING_STATE_VISIBLE
+                                    : SCREEN_RECORDING_STATE_NOT_VISIBLE;
+                } catch (RemoteException e) {
+                    e.rethrowFromSystemServer();
+                }
+            }
+            mCallbacks.put(callback, executor);
+            return mState;
+        }
+    }
+
+    @RequiresPermission(DETECT_SCREEN_RECORDING)
+    void removeCallback(@NonNull Consumer<@ScreenRecordingState Integer> callback) {
+        synchronized (sLock) {
+            mCallbacks.remove(callback);
+            if (mCallbacks.isEmpty()) {
+                try {
+                    getWindowManagerService().unregisterScreenRecordingCallback(mCallbackNotifier);
+                } catch (RemoteException e) {
+                    e.rethrowFromSystemServer();
+                }
+                mCallbackNotifier = null;
+            }
+        }
+    }
+
+    private void notifyCallbacks(@ScreenRecordingState int state) {
+        List<Runnable> callbacks;
+        synchronized (sLock) {
+            mState = state;
+            if (mCallbacks.isEmpty()) {
+                return;
+            }
+
+            callbacks = new ArrayList<>();
+            for (int i = 0; i < mCallbacks.size(); i++) {
+                Consumer<Integer> callback = mCallbacks.keyAt(i);
+                Executor executor = mCallbacks.valueAt(i);
+                callbacks.add(() -> executor.execute(() -> callback.accept(state)));
+            }
+        }
+        final long token = Binder.clearCallingIdentity();
+        try {
+            for (int i = 0; i < callbacks.size(); i++) {
+                callbacks.get(i).run();
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 3ed0385..3c0ac06 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -168,6 +168,8 @@
             boolean isTrustedOverlay);
     private static native void nativeSetDropInputMode(
             long transactionObj, long nativeObject, int flags);
+    private static native void nativeSetCanOccludePresentation(long transactionObj,
+            long nativeObject, boolean canOccludePresentation);
     private static native void nativeSurfaceFlushJankData(long nativeSurfaceObject);
     private static native boolean nativeClearContentFrameStats(long nativeObject);
     private static native boolean nativeGetContentFrameStats(long nativeObject, WindowContentFrameStats outStats);
@@ -589,6 +591,28 @@
     public static final int DISPLAY_DECORATION = 0x00000200;
 
     /**
+     * Ignore any destination frame set on the layer. This is used when the buffer scaling mode
+     * is freeze and the destination frame is applied asynchronously with the buffer submission.
+     * This is needed to maintain compatibility for SurfaceView scaling behavior.
+     * See SurfaceView scaling behavior for more details.
+     * @hide
+     */
+    public static final int IGNORE_DESTINATION_FRAME = 0x00000400;
+
+    /**
+     * Special casing for layer that is a refresh rate indicator
+     * @hide
+     */
+    public static final int LAYER_IS_REFRESH_RATE_INDICATOR = 0x00000800;
+
+    /**
+     * Sets a property on this layer indicating that its visible region should be considered when
+     * computing TrustedPresentation Thresholds
+     * @hide
+     */
+    public static final int CAN_OCCLUDE_PRESENTATION = 0x00001000;
+
+    /**
      * Surface creation flag: Creates a surface where color components are interpreted
      * as "non pre-multiplied" by their alpha channel. Of course this flag is
      * meaningless for surfaces without an alpha channel. By default
@@ -4163,6 +4187,29 @@
         }
 
         /**
+         * Sets a property on this SurfaceControl and all its children indicating that the visible
+         * region of this SurfaceControl should be considered when computing TrustedPresentation
+         * Thresholds.
+         * <p>
+         * API Guidance:
+         * The goal of this API is to identify windows that can be used to occlude content on
+         * another window. This includes windows controlled by the user or the system. If the window
+         * is transient, like Toast or notification shade, the window should not set this flag since
+         * the user or the app cannot use the window to occlude content in a persistent manner. All
+         * apps should have this flag set.
+         * <p>
+         * The caller must hold the ACCESS_SURFACE_FLINGER permission.
+         * @hide
+         */
+        public Transaction setCanOccludePresentation(SurfaceControl sc,
+                boolean canOccludePresentation) {
+            checkPreconditions(sc);
+            final int value = (canOccludePresentation) ? CAN_OCCLUDE_PRESENTATION : 0;
+            nativeSetFlags(mNativeObject, sc.mNativeObject, value, CAN_OCCLUDE_PRESENTATION);
+            return this;
+        }
+
+        /**
          * Sends a flush jank data transaction for the given surface.
          * @hide
          */
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 0d2c2cc..2366ff7 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5537,10 +5537,20 @@
      */
     private static final float FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD = 0.07f;
 
+
+    private static final long INFREQUENT_UPDATE_INTERVAL_MILLIS = 100;
+    private static final int INFREQUENT_UPDATE_COUNTS = 2;
+
     // The preferred frame rate of the view that is mainly used for
     // touch boosting, view velocity handling, and TextureView.
     private float mPreferredFrameRate = REQUESTED_FRAME_RATE_CATEGORY_DEFAULT;
 
+    private int mInfrequentUpdateCount = 0;
+    private long mLastUpdateTimeMillis = 0;
+    private long mMinusOneFrameIntervalMillis = 0;
+    private long mMinusTwoFrameIntervalMillis = 0;
+    private int mLastFrameRateCategory = FRAME_RATE_CATEGORY_HIGH;
+
     @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
     public static final float REQUESTED_FRAME_RATE_CATEGORY_DEFAULT = 0;
     @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
@@ -20253,7 +20263,10 @@
         }
 
         // For VRR to vote the preferred frame rate
-        votePreferredFrameRate();
+        if (sToolkitSetFrameRateReadOnlyFlagValue) {
+            updateInfrequentCount();
+            votePreferredFrameRate();
+        }
 
         // Reset content capture caches
         mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK;
@@ -20358,7 +20371,10 @@
     protected void damageInParent() {
         if (mParent != null && mAttachInfo != null) {
             // For VRR to vote the preferred frame rate
-            votePreferredFrameRate();
+            if (sToolkitSetFrameRateReadOnlyFlagValue) {
+                updateInfrequentCount();
+                votePreferredFrameRate();
+            }
             mParent.onDescendantInvalidated(this, this);
         }
     }
@@ -33131,11 +33147,20 @@
     }
 
     private int calculateFrameRateCategory(float sizePercentage) {
-        if (sizePercentage <= FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD) {
-            return FRAME_RATE_CATEGORY_LOW;
-        } else {
+        if (mMinusTwoFrameIntervalMillis + mMinusOneFrameIntervalMillis
+                < INFREQUENT_UPDATE_INTERVAL_MILLIS) {
+            if (sizePercentage <= FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD) {
+                return FRAME_RATE_CATEGORY_NORMAL;
+            } else {
+                return FRAME_RATE_CATEGORY_HIGH;
+            }
+        }
+
+        if (mInfrequentUpdateCount == INFREQUENT_UPDATE_COUNTS) {
             return FRAME_RATE_CATEGORY_NORMAL;
         }
+
+        return mLastFrameRateCategory;
     }
 
     private void votePreferredFrameRate() {
@@ -33144,22 +33169,22 @@
         float sizePercentage = getSizePercentage();
         int frameRateCateogry = calculateFrameRateCategory(sizePercentage);
         if (viewRootImpl != null && sizePercentage > 0) {
-            if (sToolkitSetFrameRateReadOnlyFlagValue) {
-                if (mPreferredFrameRate < 0) {
-                    if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
-                        frameRateCateogry = FRAME_RATE_CATEGORY_NO_PREFERENCE;
-                    } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) {
-                        frameRateCateogry = FRAME_RATE_CATEGORY_LOW;
-                    } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) {
-                        frameRateCateogry = FRAME_RATE_CATEGORY_NORMAL;
-                    } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) {
-                        frameRateCateogry = FRAME_RATE_CATEGORY_HIGH;
-                    }
-                } else {
-                    viewRootImpl.votePreferredFrameRate(mPreferredFrameRate);
+            if (mPreferredFrameRate < 0) {
+                if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
+                    frameRateCateogry = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+                } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) {
+                    frameRateCateogry = FRAME_RATE_CATEGORY_LOW;
+                } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) {
+                    frameRateCateogry = FRAME_RATE_CATEGORY_NORMAL;
+                } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) {
+                    frameRateCateogry = FRAME_RATE_CATEGORY_HIGH;
                 }
-                viewRootImpl.votePreferredFrameRateCategory(frameRateCateogry);
+            } else {
+                viewRootImpl.votePreferredFrameRate(mPreferredFrameRate);
             }
+            viewRootImpl.votePreferredFrameRateCategory(frameRateCateogry);
+            mLastFrameRateCategory = frameRateCateogry;
+
             if (sToolkitMetricsForFrameRateDecisionFlagValue) {
                 viewRootImpl.recordViewPercentage(sizePercentage);
             }
@@ -33238,4 +33263,27 @@
         }
         return 0;
     }
+
+    /**
+     * This function is mainly used for migrating infrequent layer lagic
+     * from SurfaceFlinger to Toolkit.
+     * The infrequent layter logic includes:
+     * - NORMAL for infrequent update: FT2-FT1 > 100 && FT3-FT2 > 100.
+     * - HIGH/NORMAL based on size for frequent update: (FT3-FT2) + (FT2 - FT1) < 100.
+     * - otherwise, use the previous category value.
+     */
+    private void updateInfrequentCount() {
+        long currentTimeMillis = AnimationUtils.currentAnimationTimeMillis();
+        long timeIntervalMillis = currentTimeMillis - mLastUpdateTimeMillis;
+        mMinusTwoFrameIntervalMillis = mMinusOneFrameIntervalMillis;
+        mMinusOneFrameIntervalMillis = timeIntervalMillis;
+
+        mLastUpdateTimeMillis = currentTimeMillis;
+        if (timeIntervalMillis >= INFREQUENT_UPDATE_INTERVAL_MILLIS) {
+            mInfrequentUpdateCount = mInfrequentUpdateCount == INFREQUENT_UPDATE_COUNTS
+                        ? mInfrequentUpdateCount : mInfrequentUpdateCount + 1;
+        } else {
+            mInfrequentUpdateCount = 0;
+        }
+    }
 }
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 25e0eca..4f1fb40 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -7,7 +7,7 @@
  *
  *      http://www.apache.org/licenses/LICENSE-2.0
  *
- * Unless required by applicable law or agreed to in writing, software
+ * Unless required by applicable law or agreed to in writing, softwareViewDebug
  * 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
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import static com.android.internal.util.Preconditions.checkArgument;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.TestApi;
@@ -34,10 +36,13 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.util.Base64;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.TypedValue;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import libcore.util.HexEncoding;
 
 import java.io.BufferedOutputStream;
@@ -54,9 +59,11 @@
 import java.lang.annotation.Target;
 import java.lang.reflect.AccessibleObject;
 import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Member;
 import java.lang.reflect.Method;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayDeque;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -67,7 +74,6 @@
 import java.util.concurrent.FutureTask;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicReference;
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.function.Function;
 import java.util.stream.Stream;
@@ -76,6 +82,9 @@
  * Various debugging/tracing tools related to {@link View} and the view hierarchy.
  */
 public class ViewDebug {
+
+    private static final String TAG = "ViewDebug";
+
     /**
      * @deprecated This flag is now unused
      */
@@ -425,6 +434,7 @@
     private static final String REMOTE_PROFILE = "PROFILE";
     private static final String REMOTE_COMMAND_CAPTURE_LAYERS = "CAPTURE_LAYERS";
     private static final String REMOTE_COMMAND_OUTPUT_DISPLAYLIST = "OUTPUT_DISPLAYLIST";
+    private static final String REMOTE_COMMAND_INVOKE_METHOD = "INVOKE_METHOD";
 
     private static HashMap<Class<?>, PropertyInfo<ExportedProperty, ?>[]> sExportProperties;
     private static HashMap<Class<?>, PropertyInfo<CapturedViewProperty, ?>[]>
@@ -555,6 +565,8 @@
                 requestLayout(view, params[0]);
             } else if (REMOTE_PROFILE.equalsIgnoreCase(command)) {
                 profile(view, clientStream, params[0]);
+            } else if (REMOTE_COMMAND_INVOKE_METHOD.equals(command)) {
+                invokeViewMethod(view, clientStream, params);
             }
         }
     }
@@ -1825,46 +1837,84 @@
         Log.d(tag, sb.toString());
     }
 
+    private static void invokeViewMethod(View root, OutputStream clientStream, String[] params)
+            throws IOException {
+        BufferedWriter out = new BufferedWriter(new OutputStreamWriter(clientStream), 32 * 1024);
+        try {
+            if (params.length < 2) {
+                throw new IllegalArgumentException("Missing parameter");
+            }
+            View targetView = findView(root, params[0]);
+            if (targetView == null) {
+                throw new IllegalArgumentException("View not found: " + params[0]);
+            }
+            String method = params[1];
+            ByteBuffer args = ByteBuffer.wrap(params.length < 2
+                    ? new byte[0]
+                    : Base64.decode(params[2], Base64.NO_WRAP));
+            byte[] result = invokeViewMethod(targetView, method, args);
+            out.write("1");
+            out.newLine();
+            out.write(Base64.encodeToString(result, Base64.NO_WRAP));
+            out.newLine();
+        } catch (Exception e) {
+            out.write("-1");
+            out.newLine();
+            out.write(e.getMessage());
+            out.newLine();
+        } finally {
+            out.close();
+        }
+    }
+
     /**
      * Invoke a particular method on given view.
      * The given method is always invoked on the UI thread. The caller thread will stall until the
      * method invocation is complete. Returns an object equal to the result of the method
      * invocation, null if the method is declared to return void
+     * @param params all the method parameters encoded in a byteArray
      * @throws Exception if the method invocation caused any exception
      * @hide
      */
-    public static Object invokeViewMethod(final View view, final Method method,
-            final Object[] args) {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final AtomicReference<Object> result = new AtomicReference<Object>();
-        final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
+    public static byte[] invokeViewMethod(View targetView, String methodName, ByteBuffer params)
+            throws ViewMethodInvocationSerializationException {
+        Class<?>[] argTypes;
+        Object[] args;
+        if (!params.hasRemaining()) {
+            argTypes = new Class<?>[0];
+            args = new Object[0];
+        } else {
+            int nArgs = params.getInt();
+            argTypes = new Class<?>[nArgs];
+            args = new Object[nArgs];
 
-        view.post(new Runnable() {
-            @Override
-            public void run() {
-                try {
-                    result.set(method.invoke(view, args));
-                } catch (InvocationTargetException e) {
-                    exception.set(e.getCause());
-                } catch (Exception e) {
-                    exception.set(e);
-                }
+            deserializeMethodParameters(args, argTypes, params);
+        }
 
-                latch.countDown();
-            }
-        });
+        Method method;
+        try {
+            method = targetView.getClass().getMethod(methodName, argTypes);
+        } catch (NoSuchMethodException e) {
+            Log.e(TAG, "No such method: " + e.getMessage());
+            throw new ViewMethodInvocationSerializationException(
+                    "No such method: " + e.getMessage());
+        }
 
         try {
-            latch.await();
-        } catch (InterruptedException e) {
-            throw new RuntimeException(e);
+            // Invoke the method on Views handler
+            FutureTask<Object> task = new FutureTask<>(() -> method.invoke(targetView, args));
+            targetView.post(task);
+            Object result = task.get();
+            Class<?> returnType = method.getReturnType();
+            return serializeReturnValue(returnType, returnType.cast(result));
+        } catch (Exception e) {
+            Log.e(TAG, "Exception while invoking method: " + e.getCause().getMessage());
+            String msg = e.getCause().getMessage();
+            if (msg == null) {
+                msg = e.getCause().toString();
+            }
+            throw new RuntimeException(msg);
         }
-
-        if (exception.get() != null) {
-            throw new RuntimeException(exception.get());
-        }
-
-        return result.get();
     }
 
     /**
@@ -1961,4 +2011,175 @@
          */
         Bitmap createBitmap();
     }
+
+    /**
+     * Deserializes parameters according to the VUOP_INVOKE_VIEW_METHOD protocol the {@code in}
+     * buffer.
+     *
+     * The length of {@code args} determines how many arguments are read. The {@code argTypes} must
+     * be the same length, and will be set to the argument types of the data read.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public static void deserializeMethodParameters(
+            Object[] args, Class<?>[] argTypes, ByteBuffer in) throws
+            ViewMethodInvocationSerializationException {
+        checkArgument(args.length == argTypes.length);
+
+        for (int i = 0; i < args.length; i++) {
+            char typeSignature = in.getChar();
+            boolean isArray = typeSignature == SIG_ARRAY;
+            if (isArray) {
+                char arrayType = in.getChar();
+                if (arrayType != SIG_BYTE) {
+                    // This implementation only supports byte-arrays for now.
+                    throw new ViewMethodInvocationSerializationException(
+                            "Unsupported array parameter type (" + typeSignature
+                                    + ") to invoke view method @argument " + i);
+                }
+
+                int arrayLength = in.getInt();
+                if (arrayLength > in.remaining()) {
+                    // The sender did not actually sent the specified amount of bytes. This
+                    // avoids a malformed packet to trigger an out-of-memory error.
+                    throw new BufferUnderflowException();
+                }
+
+                byte[] byteArray = new byte[arrayLength];
+                in.get(byteArray);
+
+                argTypes[i] = byte[].class;
+                args[i] = byteArray;
+            } else {
+                switch (typeSignature) {
+                    case SIG_BOOLEAN:
+                        argTypes[i] = boolean.class;
+                        args[i] = in.get() != 0;
+                        break;
+                    case SIG_BYTE:
+                        argTypes[i] = byte.class;
+                        args[i] = in.get();
+                        break;
+                    case SIG_CHAR:
+                        argTypes[i] = char.class;
+                        args[i] = in.getChar();
+                        break;
+                    case SIG_SHORT:
+                        argTypes[i] = short.class;
+                        args[i] = in.getShort();
+                        break;
+                    case SIG_INT:
+                        argTypes[i] = int.class;
+                        args[i] = in.getInt();
+                        break;
+                    case SIG_LONG:
+                        argTypes[i] = long.class;
+                        args[i] = in.getLong();
+                        break;
+                    case SIG_FLOAT:
+                        argTypes[i] = float.class;
+                        args[i] = in.getFloat();
+                        break;
+                    case SIG_DOUBLE:
+                        argTypes[i] = double.class;
+                        args[i] = in.getDouble();
+                        break;
+                    case SIG_STRING: {
+                        argTypes[i] = String.class;
+                        int stringUtf8ByteCount = Short.toUnsignedInt(in.getShort());
+                        byte[] rawStringBuffer = new byte[stringUtf8ByteCount];
+                        in.get(rawStringBuffer);
+                        args[i] = new String(rawStringBuffer, StandardCharsets.UTF_8);
+                        break;
+                    }
+                    default:
+                        Log.e(TAG, "arg " + i + ", unrecognized type: " + typeSignature);
+                        throw new ViewMethodInvocationSerializationException(
+                                "Unsupported parameter type (" + typeSignature
+                                        + ") to invoke view method.");
+                }
+            }
+
+        }
+    }
+
+    /**
+     * Serializes {@code value} to the wire protocol of VUOP_INVOKE_VIEW_METHOD.
+     * @hide
+     */
+    @VisibleForTesting
+    public static byte[] serializeReturnValue(Class<?> type, Object value)
+            throws ViewMethodInvocationSerializationException, IOException {
+        ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream(1024);
+        DataOutputStream dos = new DataOutputStream(byteOutStream);
+
+        if (type.isArray()) {
+            if (!type.equals(byte[].class)) {
+                // Only byte arrays are supported currently.
+                throw new ViewMethodInvocationSerializationException(
+                        "Unsupported array return type (" + type + ")");
+            }
+            byte[] byteArray = (byte[]) value;
+            dos.writeChar(SIG_ARRAY);
+            dos.writeChar(SIG_BYTE);
+            dos.writeInt(byteArray.length);
+            dos.write(byteArray);
+        } else if (boolean.class.equals(type)) {
+            dos.writeChar(SIG_BOOLEAN);
+            dos.write((boolean) value ? 1 : 0);
+        } else if (byte.class.equals(type)) {
+            dos.writeChar(SIG_BYTE);
+            dos.writeByte((byte) value);
+        } else if (char.class.equals(type)) {
+            dos.writeChar(SIG_CHAR);
+            dos.writeChar((char) value);
+        } else if (short.class.equals(type)) {
+            dos.writeChar(SIG_SHORT);
+            dos.writeShort((short) value);
+        } else if (int.class.equals(type)) {
+            dos.writeChar(SIG_INT);
+            dos.writeInt((int) value);
+        } else if (long.class.equals(type)) {
+            dos.writeChar(SIG_LONG);
+            dos.writeLong((long) value);
+        } else if (double.class.equals(type)) {
+            dos.writeChar(SIG_DOUBLE);
+            dos.writeDouble((double) value);
+        } else if (float.class.equals(type)) {
+            dos.writeChar(SIG_FLOAT);
+            dos.writeFloat((float) value);
+        } else if (String.class.equals(type)) {
+            dos.writeChar(SIG_STRING);
+            dos.writeUTF(value != null ? (String) value : "");
+        } else {
+            dos.writeChar(SIG_VOID);
+        }
+
+        return byteOutStream.toByteArray();
+    }
+
+    // Prefixes for simple primitives. These match the JNI definitions.
+    private static final char SIG_ARRAY = '[';
+    private static final char SIG_BOOLEAN = 'Z';
+    private static final char SIG_BYTE = 'B';
+    private static final char SIG_SHORT = 'S';
+    private static final char SIG_CHAR = 'C';
+    private static final char SIG_INT = 'I';
+    private static final char SIG_LONG = 'J';
+    private static final char SIG_FLOAT = 'F';
+    private static final char SIG_DOUBLE = 'D';
+    private static final char SIG_VOID = 'V';
+    // Prefixes for some commonly used objects
+    private static final char SIG_STRING = 'R';
+
+    /**
+     * @hide
+     */
+    @VisibleForTesting
+    public static class ViewMethodInvocationSerializationException extends Exception {
+        ViewMethodInvocationSerializationException(String message) {
+            super(message);
+        }
+    }
 }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index ae9c109..c27b2b1 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1025,6 +1025,13 @@
     // time for revaluating the idle status before lowering the frame rate.
     private static final int FRAME_RATE_IDLENESS_REEVALUATE_TIME = 500;
 
+    /*
+     * the variables below are used to determine whther a dVRR feature should be enabled
+     */
+
+    // Used to determine whether to suppress boost on typing
+    private boolean mShouldSuppressBoostOnTyping = false;
+
     /**
      * A temporary object used so relayoutWindow can return the latest SyncSeqId
      * system. The SyncSeqId system was designed to work without synchronous relayout
@@ -12285,7 +12292,7 @@
         boolean desiredAction = motionEventAction == MotionEvent.ACTION_DOWN
                 || motionEventAction == MotionEvent.ACTION_MOVE
                 || motionEventAction == MotionEvent.ACTION_UP;
-        boolean undesiredType = windowType == TYPE_INPUT_METHOD;
+        boolean undesiredType = windowType == TYPE_INPUT_METHOD && mShouldSuppressBoostOnTyping;
         // use toolkitSetFrameRate flag to gate the change
         return desiredAction && !undesiredType && sToolkitSetFrameRateReadOnlyFlagValue
                 && getFrameRateBoostOnTouchEnabled();
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index c788261..38cf490 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -128,8 +128,10 @@
 
 import com.android.window.flags.Flags;
 
+import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -6117,4 +6119,65 @@
         throw new UnsupportedOperationException(
                 "getDefaultToken is not implemented");
     }
+
+    /** @hide */
+    @Target(ElementType.TYPE_USE)
+    @IntDef(
+            prefix = {"SCREEN_RECORDING_STATE"},
+            value = {SCREEN_RECORDING_STATE_NOT_VISIBLE, SCREEN_RECORDING_STATE_VISIBLE})
+    @Retention(RetentionPolicy.SOURCE)
+    @interface ScreenRecordingState {}
+
+    /** Indicates the app that registered the callback is not visible in screen recording. */
+    @FlaggedApi(com.android.window.flags.Flags.FLAG_SCREEN_RECORDING_CALLBACKS)
+    int SCREEN_RECORDING_STATE_NOT_VISIBLE = 0;
+
+    /** Indicates the app that registered the callback is visible in screen recording. */
+    @FlaggedApi(com.android.window.flags.Flags.FLAG_SCREEN_RECORDING_CALLBACKS)
+    int SCREEN_RECORDING_STATE_VISIBLE = 1;
+
+    /**
+     * Adds a screen recording callback. The callback will be invoked whenever the app becomes
+     * visible in screen recording or was visible in screen recording and becomes invisible in
+     * screen recording.
+     *
+     * <p>An app is considered visible in screen recording if any activities owned by the
+     * registering process's UID are being recorded.
+     *
+     * <p>Example:
+     *
+     * <pre>
+     * windowManager.addScreenRecordingCallback(state -> {
+     *     // handle change in screen recording state
+     * });
+     * </pre>
+     *
+     * @param executor The executor on which callback method will be invoked.
+     * @param callback The callback that will be invoked when screen recording visibility changes.
+     * @return the current screen recording state.
+     * @see #SCREEN_RECORDING_STATE_NOT_VISIBLE
+     * @see #SCREEN_RECORDING_STATE_VISIBLE
+     */
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    @RequiresPermission(permission.DETECT_SCREEN_RECORDING)
+    @FlaggedApi(com.android.window.flags.Flags.FLAG_SCREEN_RECORDING_CALLBACKS)
+    default @ScreenRecordingState int addScreenRecordingCallback(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<@ScreenRecordingState Integer> callback) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Removes a screen recording callback.
+     *
+     * @param callback The callback to remove.
+     * @see #addScreenRecordingCallback(Executor, Consumer)
+     */
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    @RequiresPermission(permission.DETECT_SCREEN_RECORDING)
+    @FlaggedApi(com.android.window.flags.Flags.FLAG_SCREEN_RECORDING_CALLBACKS)
+    default void removeScreenRecordingCallback(
+            @NonNull Consumer<@ScreenRecordingState Integer> callback) {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 5072ad7..eaf45c4 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -20,6 +20,8 @@
 import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
 import static android.window.WindowProviderService.isWindowProviderService;
 
+import static com.android.window.flags.Flags.screenRecordingCallbacks;
+
 import android.annotation.CallbackExecutor;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -551,4 +553,25 @@
     public IBinder getSurfaceControlInputClientToken(@NonNull SurfaceControl surfaceControl) {
         return mGlobal.getSurfaceControlInputClientToken(surfaceControl);
     }
+
+    @Override
+    public @ScreenRecordingState int addScreenRecordingCallback(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<@ScreenRecordingState Integer> callback) {
+        if (screenRecordingCallbacks()) {
+            Objects.requireNonNull(executor, "executor must not be null");
+            Objects.requireNonNull(callback, "callback must not be null");
+            return ScreenRecordingCallbacks.getInstance().addCallback(executor, callback);
+        }
+        return SCREEN_RECORDING_STATE_NOT_VISIBLE;
+    }
+
+    @Override
+    public void removeScreenRecordingCallback(
+            @NonNull Consumer<@ScreenRecordingState Integer> callback) {
+        if (screenRecordingCallbacks()) {
+            Objects.requireNonNull(callback, "callback must not be null");
+            ScreenRecordingCallbacks.getInstance().removeCallback(callback);
+        }
+    }
 }
diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig
index 0aa516e..9d613bc 100644
--- a/core/java/android/view/flags/refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/refresh_rate_flags.aconfig
@@ -50,4 +50,28 @@
     description: "Feature flag for toolkit metrics collecting for frame rate decision"
     bug: "301343249"
     is_fixed_read_only: true
+}
+
+flag {
+    name: "toolkit_frame_rate_default_normal_read_only"
+    namespace: "toolkit"
+    description: "Feature flag for setting frame rate category as NORMAL for default"
+    bug: "239979904"
+    is_fixed_read_only: true
+}
+
+flag {
+    name: "toolkit_frame_rate_by_size_read_only"
+    namespace: "toolkit"
+    description: "Feature flag for setting frame rate category based on size"
+    bug: "239979904"
+    is_fixed_read_only: true
+}
+
+flag {
+    name: "toolkit_frame_rate_velocity_mapping_read_only"
+    namespace: "toolkit"
+    description: "Feature flag for setting frame rate based on velocity"
+    bug: "239979904"
+    is_fixed_read_only: true
 }
\ No newline at end of file
diff --git a/core/java/android/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java
index c6271d2..2f765ae 100644
--- a/core/java/android/webkit/URLUtil.java
+++ b/core/java/android/webkit/URLUtil.java
@@ -16,7 +16,6 @@
 
 package android.webkit;
 
-import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.Compatibility;
@@ -47,8 +46,7 @@
      */
     @ChangeId
     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
-    @FlaggedApi(android.os.Flags.FLAG_ANDROID_OS_BUILD_VANILLA_ICE_CREAM)
-    public static final long PARSE_CONTENT_DISPOSITION_USING_RFC_6266 = 319400769L;
+    static final long PARSE_CONTENT_DISPOSITION_USING_RFC_6266 = 319400769L;
 
     private static final String LOGTAG = "webkit";
     private static final boolean TRACE = false;
diff --git a/core/java/android/widget/OWNERS b/core/java/android/widget/OWNERS
index e20357fa..1dc90ed 100644
--- a/core/java/android/widget/OWNERS
+++ b/core/java/android/widget/OWNERS
@@ -15,3 +15,5 @@
 per-file Remote* = file:../appwidget/OWNERS
 
 per-file Toast.java = juliacr@google.com, jeffdq@google.com
+
+per-file flags/notification_widget_flags.aconfig = juliacr@google.com, jeffdq@google.com
diff --git a/core/java/com/android/internal/widget/EmphasizedNotificationButton.java b/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
index ce6af49..5cda3f2 100644
--- a/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
+++ b/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
@@ -16,16 +16,30 @@
 
 package com.android.internal.widget;
 
+import static android.app.Notification.CallStyle.DEBUG_NEW_ACTION_LAYOUT;
+import static android.app.Notification.CallStyle.USE_NEW_ACTION_LAYOUT;
+import static android.text.style.DynamicDrawableSpan.ALIGN_CENTER;
+
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
 import android.graphics.BlendMode;
+import android.graphics.Canvas;
+import android.graphics.Paint;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.DrawableWrapper;
 import android.graphics.drawable.GradientDrawable;
 import android.graphics.drawable.Icon;
 import android.graphics.drawable.RippleDrawable;
+import android.text.SpannableStringBuilder;
+import android.text.TextPaint;
+import android.text.style.ImageSpan;
+import android.text.style.MetricAffectingSpan;
+import android.text.style.ReplacementSpan;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.RemotableViewMethod;
 import android.widget.Button;
 import android.widget.RemoteViews;
@@ -43,6 +57,14 @@
     private final GradientDrawable mBackground;
     private boolean mPriority;
 
+    private int mInitialDrawablePadding;
+    private int mIconSize;
+
+    private Drawable mIconToGlue;
+    private CharSequence mLabelToGlue;
+    private int mGluedLayoutDirection = LAYOUT_DIRECTION_UNDEFINED;
+    private boolean mGluePending;
+
     public EmphasizedNotificationButton(Context context) {
         this(context, null);
     }
@@ -58,10 +80,25 @@
     public EmphasizedNotificationButton(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
+
         mRipple = (RippleDrawable) getBackground();
         mRipple.mutate();
         DrawableWrapper inset = (DrawableWrapper) mRipple.getDrawable(0);
         mBackground = (GradientDrawable) inset.getDrawable();
+
+        mIconSize = mContext.getResources().getDimensionPixelSize(
+                R.dimen.notification_actions_icon_drawable_size);
+
+        try (TypedArray typedArray = context.obtainStyledAttributes(
+                attrs, android.R.styleable.TextView, defStyleAttr, defStyleRes)) {
+            mInitialDrawablePadding = typedArray.getDimensionPixelSize(
+                    android.R.styleable.TextView_drawablePadding, 0);
+        }
+
+        if (DEBUG_NEW_ACTION_LAYOUT) {
+            Log.v(TAG, "iconSize = " + mIconSize + "px, "
+                    + "initialDrawablePadding = " + mInitialDrawablePadding + "px");
+        }
     }
 
     @RemotableViewMethod
@@ -95,19 +132,248 @@
         return () -> setImageDrawable(drawable);
     }
 
-    private void setImageDrawable(Drawable drawable) {
+    private void setImageDrawable(@Nullable Drawable drawable) {
         if (drawable != null) {
-            drawable.mutate();
-            drawable.setTintList(getTextColors());
-            drawable.setTintBlendMode(BlendMode.SRC_IN);
-            int iconSize = mContext.getResources().getDimensionPixelSize(
-                    R.dimen.notification_actions_icon_drawable_size);
-            drawable.setBounds(0, 0, iconSize, iconSize);
+            prepareIcon(drawable);
         }
         setCompoundDrawablesRelative(drawable, null, null, null);
     }
 
     /**
+     * Sets an icon to be 'glued' to the label when this button is displayed, so the icon will stay
+     * with the text if the button is wider than needed and the text isn't start-aligned.
+     *
+     * As with {@link #setImageIcon(Icon)}, the Icon will have its size constrained and will be set
+     * to the same color as the text, and this must be called after {@link #setTextColor(int)} for
+     * the latter to work.
+     *
+     * This must be called along with {@link #glueLabel(CharSequence)}, in any order, before the
+     * button is displayed.
+     */
+    @RemotableViewMethod(asyncImpl = "glueIconAsync")
+    public void glueIcon(@Nullable Icon icon) {
+        final Drawable drawable = icon == null ? null : icon.loadDrawable(mContext);
+        setIconToGlue(drawable);
+    }
+
+    /**
+     * @hide
+     */
+    @RemotableViewMethod
+    public Runnable glueIconAsync(@Nullable Icon icon) {
+        final Drawable drawable = icon == null ? null : icon.loadDrawable(mContext);
+        return () -> setIconToGlue(drawable);
+    }
+
+    private void setIconToGlue(@Nullable Drawable icon) {
+        if (!USE_NEW_ACTION_LAYOUT) {
+            Log.e(TAG, "glueIcon: new action layout disabled; doing nothing");
+            return;
+        }
+
+        prepareIcon(icon);
+
+        mIconToGlue = icon;
+        mGluePending = true;
+
+        glueIconAndLabelIfNeeded();
+    }
+
+    private void prepareIcon(@NonNull Drawable drawable) {
+        drawable.mutate();
+        drawable.setTintList(getTextColors());
+        drawable.setTintBlendMode(BlendMode.SRC_IN);
+        drawable.setBounds(0, 0, mIconSize, mIconSize);
+    }
+
+    /**
+     * Sets a label to be 'glued' to the icon when this button is displayed, so the icon will stay
+     * with the text if the button is wider than needed and the text isn't start-aligned.
+     *
+     * This must be called along with {@link #glueIcon(Icon)}, in any order, before the button is
+     * displayed.
+     */
+    @RemotableViewMethod(asyncImpl = "glueLabelAsync")
+    public void glueLabel(@Nullable CharSequence label) {
+        setLabelToGlue(label);
+    }
+
+    /**
+     * @hide
+     */
+    @RemotableViewMethod
+    public Runnable glueLabelAsync(@Nullable CharSequence label) {
+        return () -> setLabelToGlue(label);
+    }
+
+    private void setLabelToGlue(@Nullable CharSequence label) {
+        if (!USE_NEW_ACTION_LAYOUT) {
+            Log.e(TAG, "glueLabel: new action layout disabled; doing nothing");
+            return;
+        }
+
+        mLabelToGlue = label;
+        mGluePending = true;
+
+        glueIconAndLabelIfNeeded();
+    }
+
+    @Override
+    public void onRtlPropertiesChanged(int layoutDirection) {
+        super.onRtlPropertiesChanged(layoutDirection);
+
+        if (DEBUG_NEW_ACTION_LAYOUT) {
+            Log.v(TAG, "onRtlPropertiesChanged: layoutDirection = " + layoutDirection + ", "
+                    + "gluedLayoutDirection = " + mGluedLayoutDirection);
+        }
+
+        if (layoutDirection != mGluedLayoutDirection) {
+            if (DEBUG_NEW_ACTION_LAYOUT) {
+                Log.d(TAG, "onRtlPropertiesChanged: layout direction changed; regluing");
+            }
+            mGluePending = true;
+        }
+
+        glueIconAndLabelIfNeeded();
+    }
+
+    private void glueIconAndLabelIfNeeded() {
+        // Don't need to glue:
+
+        if (!mGluePending) {
+            if (DEBUG_NEW_ACTION_LAYOUT) {
+                Log.v(TAG, "glueIconAndLabelIfNeeded: glue not pending; doing nothing");
+            }
+            return;
+        }
+
+        if (mIconToGlue == null && mLabelToGlue == null) {
+            if (DEBUG_NEW_ACTION_LAYOUT) {
+                Log.v(TAG, "glueIconAndLabelIfNeeded: no icon or label to glue; doing nothing");
+            }
+            mGluePending = false;
+            return;
+        }
+
+        if (!USE_NEW_ACTION_LAYOUT) {
+            Log.e(TAG, "glueIconAndLabelIfNeeded: new action layout disabled; doing nothing");
+            return;
+        }
+
+        // Not ready to glue yet:
+
+        if (!isLayoutDirectionResolved()) {
+            if (DEBUG_NEW_ACTION_LAYOUT) {
+                Log.v(TAG, "glueIconAndLabelIfNeeded: "
+                        + "layout direction not resolved; doing nothing");
+            }
+            return;
+        }
+
+        // Ready to glue but don't have an icon *and* a label:
+        //
+        // (Note that this will *not* happen while the button is being initialized, since we won't
+        // be ready to glue. This can only happen if the button is initialized and displayed and
+        // *then* someone calls glueIcon or glueLabel.
+
+        if (mIconToGlue == null) {
+            Log.w(TAG, "glueIconAndLabelIfNeeded: label glued without icon; doing nothing");
+            return;
+        }
+
+        if (mLabelToGlue == null) {
+            Log.w(TAG, "glueIconAndLabelIfNeeded: icon glued without label; doing nothing");
+            return;
+        }
+
+        // Can't glue:
+
+        final int layoutDirection = getLayoutDirection();
+        if (layoutDirection != LAYOUT_DIRECTION_LTR && layoutDirection != LAYOUT_DIRECTION_RTL) {
+            Log.e(TAG, "glueIconAndLabelIfNeeded: "
+                    + "resolved layout direction neither LTR nor RTL; "
+                    + "doing nothing");
+            return;
+        }
+
+        // No excuses left, let's glue it!
+
+        glueIconAndLabel(layoutDirection);
+
+        mGluePending = false;
+        mGluedLayoutDirection = layoutDirection;
+    }
+
+    // Unicode replacement character
+    private static final String IMAGE_SPAN_TEXT = "\ufffd";
+
+    // Unicode no-break space
+    private static final String SPACER_SPAN_TEXT = "\u00a0";
+
+    private static final String LEFT_TO_RIGHT_ISOLATE = "\u2066";
+    private static final String RIGHT_TO_LEFT_ISOLATE = "\u2067";
+    private static final String FIRST_STRONG_ISOLATE = "\u2068";
+    private static final String POP_DIRECTIONAL_ISOLATE = "\u2069";
+
+    private void glueIconAndLabel(int layoutDirection) {
+        final boolean rtlLayout = layoutDirection == LAYOUT_DIRECTION_RTL;
+
+        if (DEBUG_NEW_ACTION_LAYOUT) {
+            Log.d(TAG, "glueIconAndLabel: "
+                    + "icon = " + mIconToGlue + ", "
+                    + "iconSize = " + mIconSize + "px, "
+                    + "initialDrawablePadding = " + mInitialDrawablePadding + "px, "
+                    + "labelToGlue.length = " + mLabelToGlue.length() + ", "
+                    + "rtlLayout = " + rtlLayout);
+        }
+
+        logIfTextDirectionNotFirstStrong();
+
+        final SpannableStringBuilder builder = new SpannableStringBuilder();
+
+        // The text direction of the label might not match the layout direction of the button, so
+        // wrap the entire string in a LEFT-TO-RIGHT ISOLATE or RIGHT-TO-LEFT ISOLATE to match the
+        // layout direction. This puts the icon, padding, and label in the right order.
+        builder.append(rtlLayout ? RIGHT_TO_LEFT_ISOLATE : LEFT_TO_RIGHT_ISOLATE);
+
+        appendSpan(builder, IMAGE_SPAN_TEXT, new ImageSpan(mIconToGlue, ALIGN_CENTER));
+        appendSpan(builder, SPACER_SPAN_TEXT, new SpacerSpan(mInitialDrawablePadding));
+
+        // If the text and layout directions are different, we would end up with the *label* in the
+        // wrong direction, so wrap the label in a FIRST STRONG ISOLATE. This triggers the same
+        // automatic text direction heuristic that Android uses by default.
+        builder.append(FIRST_STRONG_ISOLATE);
+
+        appendSpan(builder, mLabelToGlue, new CenterBesideImageSpan(mIconSize));
+
+        builder.append(POP_DIRECTIONAL_ISOLATE);
+        builder.append(POP_DIRECTIONAL_ISOLATE);
+
+        setText(builder);
+    }
+
+    private void logIfTextDirectionNotFirstStrong() {
+        if (!isTextDirectionResolved()) {
+            Log.e(TAG, "glueIconAndLabel: text direction not resolved; "
+                    + "letting View assume FIRST STRONG");
+        }
+        final int textDirection = getTextDirection();
+        if (textDirection != TEXT_DIRECTION_FIRST_STRONG) {
+            Log.w(TAG, "glueIconAndLabel: "
+                    + "expected text direction TEXT_DIRECTION_FIRST_STRONG "
+                    + "but found " + textDirection + "; "
+                    + "will use a FIRST STRONG ISOLATE regardless");
+        }
+    }
+
+    private void appendSpan(SpannableStringBuilder builder, CharSequence text, Object span) {
+        final int spanStart = builder.length();
+        builder.append(text);
+        final int spanEnd = builder.length();
+        builder.setSpan(span, spanStart, spanEnd, 0);
+    }
+
+    /**
      * Sets whether this view is a priority over its peers (which affects width).
      * Specifically, this is used by {@link NotificationActionListLayout} to give this view width
      * priority ahead of user-defined buttons when allocating horizontal space.
@@ -123,4 +389,104 @@
     public boolean isPriority() {
         return mPriority;
     }
+
+    private static class SpacerSpan extends ReplacementSpan {
+        private int mWidth;
+
+        SpacerSpan(int width) {
+            mWidth = width;
+
+            if (DEBUG_NEW_ACTION_LAYOUT) {
+                Log.d(TAG, "width = " + mWidth + "px");
+            }
+        }
+
+
+        @Override
+        public int getSize(@NonNull Paint paint, CharSequence text, int start, int end,
+                           @Nullable Paint.FontMetricsInt fontMetrics) {
+            if (DEBUG_NEW_ACTION_LAYOUT) {
+                Log.v(TAG, "getSize returning " + mWidth + "px");
+            }
+
+            return mWidth;
+        }
+
+        @Override
+        public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end,
+                         float x, int top, int y, int bottom, @NonNull Paint paint) {
+            if (DEBUG_NEW_ACTION_LAYOUT) {
+                Log.v(TAG, "drawing nothing");
+            }
+
+            // Draw nothing, it's a spacer.
+        }
+
+        private static final String TAG = "SpacerSpan";
+    }
+
+    private static class CenterBesideImageSpan extends MetricAffectingSpan {
+        private int mImageHeight;
+
+        private boolean mMeasured;
+        private int mBaselineShiftOffset;
+
+        CenterBesideImageSpan(int imageHeight) {
+            mImageHeight = imageHeight;
+
+            if (DEBUG_NEW_ACTION_LAYOUT) {
+                Log.d(TAG, "imageHeight = " + mImageHeight + "px");
+            }
+        }
+
+        @Override
+        public void updateMeasureState(@NonNull TextPaint textPaint) {
+            final int textHeight = (int) -textPaint.ascent();
+
+            /*
+             * We only need to shift the text *up* if the text is shorter than the image; ImageSpan
+             * with ALIGN_CENTER will shift the *image* up if the text is taller than the image.
+             */
+            if (textHeight < mImageHeight) {
+                mBaselineShiftOffset = -(mImageHeight - textHeight) / 2;
+            } else {
+                mBaselineShiftOffset = 0;
+            }
+
+            mMeasured = true;
+
+            if (DEBUG_NEW_ACTION_LAYOUT) {
+                Log.d(TAG, "updateMeasureState: "
+                        + "imageHeight = " + mImageHeight + "px, "
+                        + "textHeight = " + textHeight + "px, "
+                        + "baselineShiftOffset = " + mBaselineShiftOffset + "px");
+            }
+
+            textPaint.baselineShift += mBaselineShiftOffset;
+        }
+
+        @Override
+        public void updateDrawState(TextPaint textPaint) {
+            if (textPaint == null) {
+                Log.e(TAG, "updateDrawState: textPaint is null; doing nothing");
+                return;
+            }
+
+            if (!mMeasured) {
+                Log.e(TAG, "updateDrawState: called without measure; doing nothing");
+                return;
+            }
+
+            if (DEBUG_NEW_ACTION_LAYOUT) {
+                Log.v(TAG, "updateDrawState: "
+                        + "baselineShiftOffset = " + mBaselineShiftOffset + "px");
+            }
+
+            textPaint.baselineShift += mBaselineShiftOffset;
+        }
+
+        private static final String TAG = "CenterBesideImageSpan";
+    }
+
+    private static final String TAG = "EmphasizedNotificationButton";
 }
diff --git a/core/java/com/android/internal/widget/NotificationActionListLayout.java b/core/java/com/android/internal/widget/NotificationActionListLayout.java
index a7a69c9..69d2544 100644
--- a/core/java/com/android/internal/widget/NotificationActionListLayout.java
+++ b/core/java/com/android/internal/widget/NotificationActionListLayout.java
@@ -16,12 +16,16 @@
 
 package com.android.internal.widget;
 
+import static android.app.Notification.CallStyle.DEBUG_NEW_ACTION_LAYOUT;
+import static android.app.Notification.CallStyle.USE_NEW_ACTION_LAYOUT;
+
 import android.annotation.DimenRes;
 import android.app.Notification;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.RippleDrawable;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.Gravity;
 import android.view.RemotableViewMethod;
 import android.view.View;
@@ -41,13 +45,13 @@
  */
 @RemoteViews.RemoteView
 public class NotificationActionListLayout extends LinearLayout {
-
     private final int mGravity;
     private int mTotalWidth = 0;
     private int mExtraStartPadding = 0;
     private ArrayList<TextViewInfo> mMeasureOrderTextViews = new ArrayList<>();
     private ArrayList<View> mMeasureOrderOther = new ArrayList<>();
     private boolean mEmphasizedMode;
+    private boolean mEvenlyDividedMode;
     private int mDefaultPaddingBottom;
     private int mDefaultPaddingTop;
     private int mEmphasizedPaddingTop;
@@ -124,6 +128,42 @@
         }
     }
 
+    private int measureAndReturnEvenlyDividedWidth(int heightMeasureSpec, int innerWidth) {
+        final int numChildren = getChildCount();
+        int childMarginSum = 0;
+        for (int i = 0; i < numChildren; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+                childMarginSum += lp.leftMargin + lp.rightMargin;
+            }
+        }
+
+        final int innerWidthMinusChildMargins = innerWidth - childMarginSum;
+        final int childWidth = innerWidthMinusChildMargins / mNumNotGoneChildren;
+        final int childWidthMeasureSpec =
+                MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
+
+        if (DEBUG_NEW_ACTION_LAYOUT) {
+            Log.v(TAG, "measuring evenly divided width: "
+                    + "numChildren = " + numChildren + ", "
+                    + "innerWidth = " + innerWidth + "px, "
+                    + "childMarginSum = " + childMarginSum + "px, "
+                    + "innerWidthMinusChildMargins = " + innerWidthMinusChildMargins + "px, "
+                    + "childWidth = " + childWidth + "px, "
+                    + "childWidthMeasureSpec = " + MeasureSpec.toString(childWidthMeasureSpec));
+        }
+
+        for (int i = 0; i < numChildren; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                child.measure(childWidthMeasureSpec, heightMeasureSpec);
+            }
+        }
+
+        return innerWidth;
+    }
+
     private int measureAndGetUsedWidth(int widthMeasureSpec, int heightMeasureSpec, int innerWidth,
             boolean collapsePriorityActions) {
         final int numChildren = getChildCount();
@@ -208,11 +248,16 @@
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         countAndRebuildMeasureOrder();
         final int innerWidth = MeasureSpec.getSize(widthMeasureSpec) - mPaddingLeft - mPaddingRight;
-        int usedWidth = measureAndGetUsedWidth(widthMeasureSpec, heightMeasureSpec, innerWidth,
-                false /* collapsePriorityButtons */);
-        if (mNumPriorityChildren != 0 && usedWidth >= innerWidth) {
+        int usedWidth;
+        if (mEvenlyDividedMode) {
+            usedWidth = measureAndReturnEvenlyDividedWidth(heightMeasureSpec, innerWidth);
+        } else {
             usedWidth = measureAndGetUsedWidth(widthMeasureSpec, heightMeasureSpec, innerWidth,
-                    true /* collapsePriorityButtons */);
+                    false /* collapsePriorityButtons */);
+            if (mNumPriorityChildren != 0 && usedWidth >= innerWidth) {
+                usedWidth = measureAndGetUsedWidth(widthMeasureSpec, heightMeasureSpec, innerWidth,
+                        true /* collapsePriorityButtons */);
+            }
         }
 
         mTotalWidth = usedWidth + mPaddingRight + mPaddingLeft + mExtraStartPadding;
@@ -352,6 +397,38 @@
     }
 
     /**
+     * Sets whether the available width should be distributed evenly among the action buttons.
+     *
+     * When enabled, the available width (after subtracting this layout's padding and all of the
+     * buttons' margins) is divided by the number of (not-GONE) buttons, and each button is forced
+     * to that exact width, even if it is less <em>or more</em> width than they need.
+     *
+     * When disabled, the available width is allocated as buttons need; if that exceeds the
+     * available width, priority buttons are collapsed to just their icon to save space.
+     *
+     * @param evenlyDividedMode whether to enable evenly divided mode
+     */
+    @RemotableViewMethod
+    public void setEvenlyDividedMode(boolean evenlyDividedMode) {
+        if (evenlyDividedMode && !USE_NEW_ACTION_LAYOUT) {
+            Log.e(TAG, "setEvenlyDividedMode(true) called with new action layout disabled; "
+                    + "leaving evenly divided mode disabled");
+            return;
+        }
+
+        if (evenlyDividedMode == mEvenlyDividedMode) {
+            return;
+        }
+
+        if (DEBUG_NEW_ACTION_LAYOUT) {
+            Log.v(TAG, "evenlyDividedMode changed to " + evenlyDividedMode + "; "
+                    + "requesting layout");
+        }
+        mEvenlyDividedMode = evenlyDividedMode;
+        requestLayout();
+    }
+
+    /**
      * Set whether the list is in a mode where some actions are emphasized. This will trigger an
      * equal measuring where all actions are full height and change a few parameters like
      * the padding.
@@ -410,4 +487,5 @@
         }
     }
 
+    private static final String TAG = "NotificationActionListLayout";
 }
diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp
index ae23942..bed7768 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.cpp
+++ b/core/jni/android_hardware_input_InputWindowHandle.cpp
@@ -75,6 +75,7 @@
     jfieldID windowToken;
     jfieldID focusTransferTarget;
     jfieldID alpha;
+    jfieldID canOccludePresentation;
 } gInputWindowHandleClassInfo;
 
 static struct {
@@ -327,6 +328,8 @@
                         javaObjectForIBinder(env, windowInfo.windowToken));
 
     env->SetFloatField(inputWindowHandle, gInputWindowHandleClassInfo.alpha, windowInfo.alpha);
+    env->SetBooleanField(inputWindowHandle, gInputWindowHandleClassInfo.canOccludePresentation,
+                         windowInfo.canOccludePresentation);
 
     return inputWindowHandle;
 }
@@ -451,6 +454,9 @@
 
     GET_FIELD_ID(gInputWindowHandleClassInfo.alpha, clazz, "alpha", "F");
 
+    GET_FIELD_ID(gInputWindowHandleClassInfo.canOccludePresentation, clazz,
+                 "canOccludePresentation", "Z");
+
     jclass surfaceControlClazz;
     FIND_CLASS(surfaceControlClazz, "android/view/SurfaceControl");
     GET_FIELD_ID(gInputWindowHandleClassInfo.touchableRegionSurfaceControl.mNativeObject,
diff --git a/core/jni/android_opengl_EGL14.cpp b/core/jni/android_opengl_EGL14.cpp
index 2f29cae..917d283 100644
--- a/core/jni/android_opengl_EGL14.cpp
+++ b/core/jni/android_opengl_EGL14.cpp
@@ -17,6 +17,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include "jni.h"
diff --git a/core/jni/android_opengl_EGL15.cpp b/core/jni/android_opengl_EGL15.cpp
index b9c36b9..447b8ec 100644
--- a/core/jni/android_opengl_EGL15.cpp
+++ b/core/jni/android_opengl_EGL15.cpp
@@ -17,6 +17,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include "jni.h"
diff --git a/core/jni/android_opengl_EGLExt.cpp b/core/jni/android_opengl_EGLExt.cpp
index cdc9852..ffd75ea 100644
--- a/core/jni/android_opengl_EGLExt.cpp
+++ b/core/jni/android_opengl_EGLExt.cpp
@@ -17,6 +17,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include "jni.h"
@@ -54,11 +55,11 @@
     jclass eglsurfaceClassLocal = _env->FindClass("android/opengl/EGLSurface");
     eglsurfaceClass = (jclass) _env->NewGlobalRef(eglsurfaceClassLocal);
     jclass eglsyncClassLocal = _env->FindClass("android/opengl/EGLSync");
-    eglsyncClass = (jclass)_env->NewGlobalRef(eglsyncClassLocal);
+    eglsyncClass = (jclass) _env->NewGlobalRef(eglsyncClassLocal);
 
     egldisplayGetHandleID = _env->GetMethodID(egldisplayClass, "getNativeHandle", "()J");
     eglsurfaceGetHandleID = _env->GetMethodID(eglsurfaceClass, "getNativeHandle", "()J");
-    eglsyncGetHandleID = _env->GetMethodID(eglsyncClassLocal, "getNativeHandle", "()J");
+    eglsyncGetHandleID = _env->GetMethodID(eglsyncClass, "getNativeHandle", "()J");
 }
 
 static void *
@@ -72,6 +73,14 @@
     return reinterpret_cast<void*>(_env->CallLongMethod(obj, mid));
 }
 
+// TODO: this should be generated from the .spec file, but needs to be renamed and made private
+static jint android_eglDupNativeFenceFDANDROID(JNIEnv *env, jobject, jobject dpy, jobject sync) {
+    EGLDisplay dpy_native = (EGLDisplay)fromEGLHandle(env, egldisplayGetHandleID, dpy);
+    EGLSync sync_native = (EGLSync)fromEGLHandle(env, eglsyncGetHandleID, sync);
+
+    return eglDupNativeFenceFDANDROID(dpy_native, sync_native);
+}
+
 // --------------------------------------------------------------------------
 /* EGLBoolean eglPresentationTimeANDROID ( EGLDisplay dpy, EGLSurface sur, EGLnsecsANDROID time ) */
 static jboolean
@@ -89,21 +98,12 @@
     return (jboolean)_returnValue;
 }
 
-static jint android_eglDupNativeFenceFDANDROID(JNIEnv *env, jobject, jobject dpy, jobject sync) {
-    EGLDisplay dpy_native = (EGLDisplay)fromEGLHandle(env, egldisplayGetHandleID, dpy);
-    EGLSync sync_native = (EGLSync)fromEGLHandle(env, eglsyncGetHandleID, sync);
-
-    return eglDupNativeFenceFDANDROID(dpy_native, sync_native);
-}
-
 static const char *classPathName = "android/opengl/EGLExt";
 
 static const JNINativeMethod methods[] = {
-        {"_nativeClassInit", "()V", (void *)nativeClassInit},
-        {"eglPresentationTimeANDROID", "(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLSurface;J)Z",
-         (void *)android_eglPresentationTimeANDROID},
-        {"eglDupNativeFenceFDANDROIDImpl", "(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLSync;)I",
-         (void *)android_eglDupNativeFenceFDANDROID},
+{"_nativeClassInit", "()V", (void*)nativeClassInit },
+{"eglPresentationTimeANDROID", "(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLSurface;J)Z", (void *) android_eglPresentationTimeANDROID },
+{"eglDupNativeFenceFDANDROIDImpl", "(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLSync;)I", (void *)android_eglDupNativeFenceFDANDROID },
 };
 
 int register_android_opengl_jni_EGLExt(JNIEnv *_env)
diff --git a/core/jni/android_opengl_GLES10.cpp b/core/jni/android_opengl_GLES10.cpp
index d65b498..2d921ad 100644
--- a/core/jni/android_opengl_GLES10.cpp
+++ b/core/jni/android_opengl_GLES10.cpp
@@ -18,6 +18,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <GLES/gl.h>
diff --git a/core/jni/android_opengl_GLES10Ext.cpp b/core/jni/android_opengl_GLES10Ext.cpp
index 3638b87..35a9a68 100644
--- a/core/jni/android_opengl_GLES10Ext.cpp
+++ b/core/jni/android_opengl_GLES10Ext.cpp
@@ -18,6 +18,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <GLES/gl.h>
diff --git a/core/jni/android_opengl_GLES11.cpp b/core/jni/android_opengl_GLES11.cpp
index 9724e6c..e04b56e 100644
--- a/core/jni/android_opengl_GLES11.cpp
+++ b/core/jni/android_opengl_GLES11.cpp
@@ -18,6 +18,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <GLES/gl.h>
diff --git a/core/jni/android_opengl_GLES11Ext.cpp b/core/jni/android_opengl_GLES11Ext.cpp
index 1ffa4ec..bccbda6 100644
--- a/core/jni/android_opengl_GLES11Ext.cpp
+++ b/core/jni/android_opengl_GLES11Ext.cpp
@@ -18,6 +18,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <GLES/gl.h>
diff --git a/core/jni/android_opengl_GLES20.cpp b/core/jni/android_opengl_GLES20.cpp
index d832558..165262e 100644
--- a/core/jni/android_opengl_GLES20.cpp
+++ b/core/jni/android_opengl_GLES20.cpp
@@ -18,6 +18,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <GLES2/gl2.h>
diff --git a/core/jni/android_opengl_GLES30.cpp b/core/jni/android_opengl_GLES30.cpp
index 719c6b3..d3fe439 100644
--- a/core/jni/android_opengl_GLES30.cpp
+++ b/core/jni/android_opengl_GLES30.cpp
@@ -18,6 +18,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <GLES3/gl3.h>
diff --git a/core/jni/android_opengl_GLES31.cpp b/core/jni/android_opengl_GLES31.cpp
index afe7c63..b123f9d 100644
--- a/core/jni/android_opengl_GLES31.cpp
+++ b/core/jni/android_opengl_GLES31.cpp
@@ -17,6 +17,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <stdint.h>
diff --git a/core/jni/android_opengl_GLES31Ext.cpp b/core/jni/android_opengl_GLES31Ext.cpp
index 8127433..1e4049b 100644
--- a/core/jni/android_opengl_GLES31Ext.cpp
+++ b/core/jni/android_opengl_GLES31Ext.cpp
@@ -17,6 +17,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <GLES3/gl31.h>
diff --git a/core/jni/android_opengl_GLES32.cpp b/core/jni/android_opengl_GLES32.cpp
index 7ed7548..e0175f0 100644
--- a/core/jni/android_opengl_GLES32.cpp
+++ b/core/jni/android_opengl_GLES32.cpp
@@ -17,6 +17,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <stdint.h>
diff --git a/core/jni/com_google_android_gles_jni_GLImpl.cpp b/core/jni/com_google_android_gles_jni_GLImpl.cpp
index 21de723..ef29c88 100644
--- a/core/jni/com_google_android_gles_jni_GLImpl.cpp
+++ b/core/jni/com_google_android_gles_jni_GLImpl.cpp
@@ -18,6 +18,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include "jni.h"
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 58cee98..6be1be4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -881,6 +881,19 @@
         android:description="@string/permdesc_writeContacts"
       android:protectionLevel="dangerous" />
 
+    <!-- Allows an app to update the verification status of E2EE contact keys owned by other apps.
+        <p>This permission is only granted to system apps.
+        <p>Protection level: signature|privileged
+        @SystemApi
+        @hide
+        @FlaggedApi("android.provider.user_keys")
+    -->
+    <permission android:name="android.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS"
+                android:permissionGroup="android.permission-group.UNDEFINED"
+                android:label="@string/permlab_writeVerificationStateE2eeContactKeys"
+                android:description="@string/permdesc_writeVerificationStateE2eeContactKeys"
+                android:protectionLevel="signature|privileged" />
+
     <!-- Allows an application to set default account for new contacts.
         <p> This permission is only granted to system applications fulfilling the Contacts app role.
         <p>Protection level: internal|role
@@ -2632,6 +2645,13 @@
                 android:description="@string/permdesc_detectScreenCapture"
                 android:protectionLevel="normal" />
 
+    <!-- Allows an application to get notified when it is being recorded.
+         <p>Protection level: normal
+         @FlaggedApi("com.android.window.flags.screen_recording_callbacks")
+    -->
+    <permission android:name="android.permission.DETECT_SCREEN_RECORDING"
+                android:protectionLevel="normal" />
+
     <!-- ======================================== -->
     <!-- Permissions for factory reset protection -->
     <!-- ======================================== -->
@@ -3188,6 +3208,14 @@
     <permission android:name="android.permission.INTERACT_ACROSS_PROFILES"
         android:protectionLevel="signature|appop" />
 
+    <!-- Allows applications to access profiles with ACCESS_HIDDEN_PROFILES user property
+     <p>Protection level: normal
+     @FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles") -->
+    <permission android:name="android.permission.ACCESS_HIDDEN_PROFILES"
+        android:label="@string/permlab_accessHiddenProfile"
+        android:description="@string/permdesc_accessHiddenProfile"
+        android:protectionLevel="normal" />
+
     <!-- @SystemApi @hide Allows starting activities across profiles in the same profile group. -->
     <permission android:name="android.permission.START_CROSS_PROFILE_ACTIVITIES"
                 android:protectionLevel="signature|role" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index dd6e79e..be96cc2 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -396,6 +396,27 @@
     <!-- Displayed when the call forwarding query was set but forwarding is not enabled. -->
     <string name="cfTemplateRegisteredTime"><xliff:g id="bearer_service_code">{0}</xliff:g>: Not forwarded</string>
 
+    <!-- Title of the cellular network security safety center source's status. -->
+    <string name="scCellularNetworkSecurityTitle">Cellular network security</string>
+    <!-- Summary of the cellular network security safety center source's status. -->
+    <string name="scCellularNetworkSecuritySummary">Review settings</string>
+    <!-- Title of the safety center issue and notification when the phone's identifier is shared over the network. -->
+    <string name="scIdentifierDisclosureIssueTitle">Device identifier accessed</string>
+    <!-- Summary of the safety center issue and notification when the phone's identifier is shared over the network. -->
+    <string name="scIdentifierDisclosureIssueSummary">A network on the <xliff:g id="disclosure_network">%4$s</xliff:g> connection recorded your device\'s unique identifier (IMSI) <xliff:g id="disclosure_count">%1$d</xliff:g> times in the period between <xliff:g id="disclosure_window_start_time">%2$tr</xliff:g> and <xliff:g id="disclosure_window_end_time">%3$tr</xliff:g>.</string>
+    <!-- Title of the safety center issue and notification when the phone restores an encrypted connection to the network. -->
+    <string name="scNullCipherIssueEncryptedTitle">Encrypted connection to <xliff:g id="network_name">%1$s</xliff:g></string>
+    <!-- Summary of the safety center issue and notification when the phone restores an encrypted connection to the network. -->
+    <string name="scNullCipherIssueEncryptedSummary">You\'re now connected to a more secure cellular network.</string>
+    <!-- Title of the safety center issue and notification when a connected network is not using encryption. -->
+    <string name="scNullCipherIssueNonEncryptedTitle">Non-encrypted connection to <xliff:g id="network_name">%1$s</xliff:g></string>
+    <!-- Summary of the safety center issue and notification when a connected network is not using encryption.  -->
+    <string name="scNullCipherIssueNonEncryptedSummary">You\'re connected to a non-encrypted cellular network. Your calls, messages, and data are vulnerable to interception.</string>
+    <!-- Label for the button that links to the cellular network security settings. -->
+    <string name="scNullCipherIssueActionSettings">Cellular security settings</string>
+    <!-- Label for the button that link to education resourcess about cellular network security settings. -->
+    <string name="scNullCipherIssueActionLearnMore">Learn more</string>
+
     <!-- android.net.http Error strings --> <skip />
     <!-- Displayed when a feature code (non-phone number) is dialed and completes successfully. -->
     <string name="fcComplete">Feature code complete.</string>
@@ -1572,6 +1593,11 @@
     <string name="permdesc_setWallpaper">Allows the app to set the system wallpaper.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_accessHiddenProfile">Access hidden profiles</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_accessHiddenProfile">Allows the app to access hidden profiles.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_setWallpaperHints">adjust your wallpaper size</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_setWallpaperHints">Allows the app to set the system wallpaper size hints.</string>
@@ -2222,6 +2248,11 @@
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this.[CHAR_LIMIT=NONE] -->
     <string name="permdesc_updatePackagesWithoutUserAction">Allows the holder to update the app it previously installed without user action</string>
 
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] -->
+    <string name="permlab_writeVerificationStateE2eeContactKeys">update the verification states of E2EE contact keys owned by other apps</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this.[CHAR_LIMIT=NONE] -->
+    <string name="permdesc_writeVerificationStateE2eeContactKeys">Allows the app to update the verification states of E2EE contact keys owned by other apps</string>
+
     <!-- Policy administration -->
 
     <!-- Title of policy access to limiting the user's password choices -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 699c8ac..603b902 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -954,6 +954,16 @@
   <java-symbol type="string" name="roamingText8" />
   <java-symbol type="string" name="roamingText9" />
   <java-symbol type="string" name="roamingTextSearching" />
+  <java-symbol type="string" name="scCellularNetworkSecuritySummary" />
+  <java-symbol type="string" name="scCellularNetworkSecurityTitle" />
+  <java-symbol type="string" name="scIdentifierDisclosureIssueSummary" />
+  <java-symbol type="string" name="scIdentifierDisclosureIssueTitle" />
+  <java-symbol type="string" name="scNullCipherIssueActionLearnMore" />
+  <java-symbol type="string" name="scNullCipherIssueActionSettings" />
+  <java-symbol type="string" name="scNullCipherIssueEncryptedSummary" />
+  <java-symbol type="string" name="scNullCipherIssueEncryptedTitle" />
+  <java-symbol type="string" name="scNullCipherIssueNonEncryptedSummary" />
+  <java-symbol type="string" name="scNullCipherIssueNonEncryptedTitle" />
   <java-symbol type="string" name="selected" />
   <java-symbol type="string" name="sendText" />
   <java-symbol type="string" name="sending" />
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 513e022..1b25d7f 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -208,6 +208,7 @@
         "testng",
     ],
     srcs: [
+        "src/android/app/ActivityManagerTest.java",
         "src/android/content/pm/PackageManagerTest.java",
         "src/android/content/pm/UserInfoTest.java",
         "src/android/database/CursorWindowTest.java",
diff --git a/core/tests/coretests/src/android/app/ActivityManagerTest.java b/core/tests/coretests/src/android/app/ActivityManagerTest.java
new file mode 100644
index 0000000..d930e4d
--- /dev/null
+++ b/core/tests/coretests/src/android/app/ActivityManagerTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.os.UserHandle;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
+
+@RunWith(AndroidJUnit4.class)
+public class ActivityManagerTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+    @Test
+    public void testSimple() throws Exception {
+        assertTrue(ActivityManager.isSystemReady());
+        assertFalse(ActivityManager.isUserAMonkey());
+        assertNotEquals(UserHandle.USER_NULL, ActivityManager.getCurrentUser());
+    }
+
+    @Test
+    public void testCapabilities() throws Exception {
+        // For the moment mostly want to confirm we don't crash
+        assertNotNull(ActivityManager.getCapabilitiesSummary(~0));
+        ActivityManager.printCapabilitiesFull(new PrintWriter(new ByteArrayOutputStream()), ~0);
+        ActivityManager.printCapabilitiesSummary(new PrintWriter(new ByteArrayOutputStream()), ~0);
+        ActivityManager.printCapabilitiesSummary(new StringBuilder(), ~0);
+    }
+
+    @Test
+    public void testProcState() throws Exception {
+        // For the moment mostly want to confirm we don't crash
+        assertNotNull(ActivityManager.procStateToString(PROCESS_STATE_SERVICE));
+        assertNotNull(ActivityManager.processStateAmToProto(PROCESS_STATE_SERVICE));
+        assertTrue(ActivityManager.isProcStateBackground(PROCESS_STATE_SERVICE));
+        assertFalse(ActivityManager.isProcStateCached(PROCESS_STATE_SERVICE));
+        assertFalse(ActivityManager.isForegroundService(PROCESS_STATE_SERVICE));
+        assertFalse(ActivityManager.isProcStateConsideredInteraction(PROCESS_STATE_SERVICE));
+    }
+
+    @Test
+    public void testStartResult() throws Exception {
+        // For the moment mostly want to confirm we don't crash
+        assertTrue(ActivityManager.isStartResultSuccessful(50));
+        assertTrue(ActivityManager.isStartResultFatalError(-50));
+    }
+
+    @Test
+    public void testRestrictionLevel() throws Exception {
+        // For the moment mostly want to confirm we don't crash
+        assertNotNull(ActivityManager.restrictionLevelToName(
+                ActivityManager.RESTRICTION_LEVEL_HIBERNATION));
+    }
+}
diff --git a/core/tests/coretests/src/android/ddm/OWNERS b/core/tests/coretests/src/android/ddm/OWNERS
deleted file mode 100644
index c8be191..0000000
--- a/core/tests/coretests/src/android/ddm/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-michschn@google.com
diff --git a/core/tests/coretests/src/android/os/HandlerThreadTest.java b/core/tests/coretests/src/android/os/HandlerThreadTest.java
index 0bac1c7..1ad71da 100644
--- a/core/tests/coretests/src/android/os/HandlerThreadTest.java
+++ b/core/tests/coretests/src/android/os/HandlerThreadTest.java
@@ -28,15 +28,20 @@
 import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.Assume;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidJUnit4.class)
 public class HandlerThreadTest {
     private static final int TEST_WHAT = 1;
 
-    @Rule
+    @Rule(order = 1)
+    public ExpectedException mThrown = ExpectedException.none();
+
+    @Rule(order = 2)
     public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
     private boolean mGotMessage = false;
@@ -112,4 +117,28 @@
         assertTrue(mGotMessage);
         assertEquals(TEST_WHAT, mGotMessageWhat);
     }
+
+    /**
+     * Confirm that a background handler thread throwing an exception during a test results in a
+     * test failure being reported.
+     */
+    @Test
+    public void testUncaughtExceptionFails() throws Exception {
+        // For the moment we can only test Ravenwood; on a physical device uncaught exceptions
+        // are detected, but reported as test failures at a higher level where we can't inspect
+        Assume.assumeTrue(RavenwoodRule.isOnRavenwood());
+        mThrown.expect(IllegalStateException.class);
+
+        final HandlerThread thread = new HandlerThread("HandlerThreadTest");
+        thread.start();
+        thread.getThreadHandler().post(() -> {
+            throw new IllegalStateException();
+        });
+
+        // Wait until we've drained past the message above, then terminate test without throwing
+        // directly; the test harness should notice and report the uncaught exception
+        while (!thread.getThreadHandler().getLooper().getQueue().isIdle()) {
+            SystemClock.sleep(10);
+        }
+    }
 }
diff --git a/core/tests/coretests/src/android/os/VibrationAttributesTest.java b/core/tests/coretests/src/android/os/VibrationAttributesTest.java
new file mode 100644
index 0000000..f5a81c5
--- /dev/null
+++ b/core/tests/coretests/src/android/os/VibrationAttributesTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 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.os;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class VibrationAttributesTest {
+    @Test
+    public void testSimple() throws Exception {
+        final VibrationAttributes attr = new VibrationAttributes.Builder()
+                .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
+                .setUsage(VibrationAttributes.USAGE_ALARM)
+                .build();
+
+        assertEquals(VibrationAttributes.CATEGORY_KEYBOARD, attr.getCategory());
+        assertEquals(VibrationAttributes.USAGE_ALARM, attr.getUsage());
+    }
+}
diff --git a/core/tests/coretests/src/android/ddm/DdmHandleViewDebugTest.java b/core/tests/coretests/src/android/view/ViewDebugTest.java
similarity index 97%
rename from core/tests/coretests/src/android/ddm/DdmHandleViewDebugTest.java
rename to core/tests/coretests/src/android/view/ViewDebugTest.java
index 7248983..4522842 100644
--- a/core/tests/coretests/src/android/ddm/DdmHandleViewDebugTest.java
+++ b/core/tests/coretests/src/android/view/ViewDebugTest.java
@@ -14,17 +14,17 @@
  * limitations under the License.
  */
 
-package android.ddm;
+package android.view;
 
-import static android.ddm.DdmHandleViewDebug.deserializeMethodParameters;
-import static android.ddm.DdmHandleViewDebug.serializeReturnValue;
+import static android.view.ViewDebug.deserializeMethodParameters;
+import static android.view.ViewDebug.serializeReturnValue;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThrows;
 
-import android.ddm.DdmHandleViewDebug.ViewMethodInvocationSerializationException;
 import android.platform.test.annotations.Presubmit;
+import android.view.ViewDebug.ViewMethodInvocationSerializationException;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -39,7 +39,7 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 @Presubmit
-public final class DdmHandleViewDebugTest {
+public final class ViewDebugTest {
     // true
     private static final byte[] SERIALIZED_BOOLEAN_TRUE = {0x00, 0x5A, 1};
 
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index cf3eb12..60769c7 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -18,6 +18,7 @@
 
 import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR;
 import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
+import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY;
 import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
 import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
 import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
@@ -475,8 +476,9 @@
      * Also, mIsFrameRateBoosting should be true when the visibility becomes visible
      */
     @Test
-    @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
-    public void votePreferredFrameRate_voteFrameRateCategory_visibility() {
+    @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
+            FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY})
+    public void votePreferredFrameRate_voteFrameRateCategory_visibility_bySize() {
         View view = new View(sContext);
         attachViewToWindow(view);
         ViewRootImpl viewRootImpl = view.getViewRootImpl();
@@ -507,8 +509,9 @@
      * <7%: FRAME_RATE_CATEGORY_LOW
      */
     @Test
-    @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
-    public void votePreferredFrameRate_voteFrameRateCategory_smallSize() {
+    @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
+            FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY})
+    public void votePreferredFrameRate_voteFrameRateCategory_smallSize_bySize() {
         View view = new View(sContext);
         WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
         wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
@@ -534,8 +537,9 @@
      * >=7% : FRAME_RATE_CATEGORY_NORMAL
      */
     @Test
-    @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
-    public void votePreferredFrameRate_voteFrameRateCategory_normalSize() {
+    @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
+            FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY})
+    public void votePreferredFrameRate_voteFrameRateCategory_normalSize_bySize() {
         View view = new View(sContext);
         WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
         wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
@@ -559,6 +563,96 @@
     }
 
     /**
+     * Test the value of the frame rate cateogry based on the visibility of a view
+     * Invsible: FRAME_RATE_CATEGORY_NO_PREFERENCE
+     * Visible: FRAME_RATE_CATEGORY_HIGH
+     * Also, mIsFrameRateBoosting should be true when the visibility becomes visible
+     */
+    @Test
+    @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+    public void votePreferredFrameRate_voteFrameRateCategory_visibility_defaultHigh() {
+        View view = new View(sContext);
+        attachViewToWindow(view);
+        ViewRootImpl viewRootImpl = view.getViewRootImpl();
+        sInstrumentation.runOnMainSync(() -> {
+            view.setVisibility(View.INVISIBLE);
+            view.invalidate();
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+                    FRAME_RATE_CATEGORY_NO_PREFERENCE);
+        });
+        sInstrumentation.waitForIdleSync();
+
+        sInstrumentation.runOnMainSync(() -> {
+            view.setVisibility(View.VISIBLE);
+            view.invalidate();
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+                    FRAME_RATE_CATEGORY_HIGH);
+        });
+        sInstrumentation.waitForIdleSync();
+
+        sInstrumentation.runOnMainSync(() -> {
+            assertEquals(viewRootImpl.getIsFrameRateBoosting(), true);
+        });
+    }
+
+    /**
+     * Test the value of the frame rate cateogry based on the size of a view.
+     * The current threshold value is 7% of the screen size
+     * <7%: FRAME_RATE_CATEGORY_NORMAL
+     */
+    @Test
+    @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+    public void votePreferredFrameRate_voteFrameRateCategory_smallSize_defaultHigh() {
+        View view = new View(sContext);
+        WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
+        wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
+        wmlp.width = 1;
+        wmlp.height = 1;
+
+        sInstrumentation.runOnMainSync(() -> {
+            WindowManager wm = sContext.getSystemService(WindowManager.class);
+            wm.addView(view, wmlp);
+        });
+        sInstrumentation.waitForIdleSync();
+
+        ViewRootImpl viewRootImpl = view.getViewRootImpl();
+        sInstrumentation.runOnMainSync(() -> {
+            view.invalidate();
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
+        });
+    }
+
+    /**
+     * Test the value of the frame rate cateogry based on the size of a view.
+     * The current threshold value is 7% of the screen size
+     * >=7% : FRAME_RATE_CATEGORY_HIGH
+     */
+    @Test
+    @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+    public void votePreferredFrameRate_voteFrameRateCategory_normalSize_defaultHigh() {
+        View view = new View(sContext);
+        WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
+        wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
+
+        sInstrumentation.runOnMainSync(() -> {
+            WindowManager wm = sContext.getSystemService(WindowManager.class);
+            Display display = wm.getDefaultDisplay();
+            DisplayMetrics metrics = new DisplayMetrics();
+            display.getMetrics(metrics);
+            wmlp.width = (int) (metrics.widthPixels * 0.9);
+            wmlp.height = (int) (metrics.heightPixels * 0.9);
+            wm.addView(view, wmlp);
+        });
+        sInstrumentation.waitForIdleSync();
+
+        ViewRootImpl viewRootImpl = view.getViewRootImpl();
+        sInstrumentation.runOnMainSync(() -> {
+            view.invalidate();
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+        });
+    }
+
+    /**
      * Test how values of the frame rate cateogry are aggregated.
      * It should take the max value among all of the voted categories per frame.
      */
@@ -701,6 +795,61 @@
         });
     }
 
+    /**
+     * Test the logic of infrequent layer:
+     * - NORMAL for infrequent update: FT2-FT1 > 100 && FT3-FT2 > 100.
+     * - HIGH/NORMAL based on size for frequent update: (FT3-FT2) + (FT2 - FT1) < 100.
+     * - otherwise, use the previous category value.
+     */
+    @Test
+    @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+    public void votePreferredFrameRate_infrequentLayer_defaultHigh() throws InterruptedException {
+        final long delay = 200L;
+
+        View view = new View(sContext);
+        WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
+        wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
+
+        sInstrumentation.runOnMainSync(() -> {
+            WindowManager wm = sContext.getSystemService(WindowManager.class);
+            Display display = wm.getDefaultDisplay();
+            DisplayMetrics metrics = new DisplayMetrics();
+            display.getMetrics(metrics);
+            wmlp.width = (int) (metrics.widthPixels * 0.9);
+            wmlp.height = (int) (metrics.heightPixels * 0.9);
+            wm.addView(view, wmlp);
+        });
+        sInstrumentation.waitForIdleSync();
+
+        ViewRootImpl viewRootImpl = view.getViewRootImpl();
+
+        // Frequent update
+        sInstrumentation.runOnMainSync(() -> {
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+                    FRAME_RATE_CATEGORY_NO_PREFERENCE);
+            view.invalidate();
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+            view.invalidate();
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+            view.invalidate();
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+        });
+
+        // In transistion from frequent update to infrequent update
+        Thread.sleep(delay);
+        sInstrumentation.runOnMainSync(() -> {
+            view.invalidate();
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+        });
+
+        // Infrequent update
+        Thread.sleep(delay);
+        sInstrumentation.runOnMainSync(() -> {
+            view.invalidate();
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
+        });
+    }
+
     @Test
     public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() {
         mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 2873428..91e620c 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -185,6 +185,7 @@
         <permission name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
         <permission name="android.permission.UWB_PRIVILEGED"/>
         <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
+        <permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
     </privapp-permissions>
 
     <privapp-permissions package="com.android.providers.calendar">
@@ -571,6 +572,8 @@
         <!-- Permission required for BinaryTransparencyService shell API and host test -->
         <permission name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES" />
         <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
+        <!-- Permissions required for CTS test - CtsContactKeysProviderPrivilegedApp -->
+        <permission name="android.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.statementservice">
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index f32f030..50a58da 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -465,7 +465,7 @@
     /**
      * Call when all the views should be removed/cleaned up.
      */
-    void cleanupViews() {
+    public void cleanupViews() {
         cleanupViews(true);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index aea3ca1..d0db708 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -730,7 +730,7 @@
             // window to show this in, but we use a separate code path.
             // TODO(b/273312602): consider foldables where we do need a stack view when folded
             if (mLayerView == null) {
-                mLayerView = new BubbleBarLayerView(mContext, this);
+                mLayerView = new BubbleBarLayerView(mContext, this, mBubbleData);
                 mLayerView.setUnBubbleConversationCallback(mSysuiProxy::onUnbubbleConversation);
             }
         } else {
@@ -1714,8 +1714,7 @@
         @Override
         public void removeBubble(Bubble removedBubble) {
             if (mLayerView != null) {
-                // TODO: need to check if there's something that needs to happen here, e.g. if
-                //  the currently selected & expanded bubble is removed?
+                mLayerView.removeBubble(removedBubble);
             }
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 127c7e8..dbfa260 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -15,7 +15,6 @@
  */
 package com.android.wm.shell.bubbles;
 
-import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
 import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_DATA;
@@ -41,6 +40,7 @@
 import com.android.launcher3.icons.BubbleIconFactory;
 import com.android.wm.shell.R;
 import com.android.wm.shell.bubbles.Bubbles.DismissReason;
+import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
 import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
 import com.android.wm.shell.common.bubbles.RemovedBubble;
 
@@ -427,7 +427,7 @@
     /**
      * When this method is called it is expected that all info in the bubble has completed loading.
      * @see Bubble#inflate(BubbleViewInfoTask.Callback, Context, BubbleController, BubbleStackView,
-     * BubbleIconFactory, boolean)
+     * BubbleBarLayerView, BubbleIconFactory, boolean)
      */
     void notificationEntryUpdated(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
         if (DEBUG_BUBBLE_DATA) {
@@ -1069,7 +1069,6 @@
     /**
      * The set of bubbles in row.
      */
-    @VisibleForTesting(visibility = PACKAGE)
     public List<Bubble> getBubbles() {
         return Collections.unmodifiableList(mBubbles);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
index 5fc67d7..dc27133 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
@@ -31,6 +31,7 @@
 import android.graphics.Rect;
 import android.util.Log;
 import android.view.View;
+import android.view.ViewGroup;
 
 import androidx.annotation.Nullable;
 
@@ -186,6 +187,7 @@
             }
             if (mTaskView != null) {
                 mTaskView.release();
+                ((ViewGroup) mParentView).removeView(mTaskView);
                 mTaskView = null;
             }
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index 00d683e..73a9cf4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -266,13 +266,8 @@
         mListener.onBackPressed();
     }
 
-    /** Cleans up task view, should be called when the bubble is no longer active. */
+    /** Cleans up the expanded view, should be called when the bubble is no longer active. */
     public void cleanUpExpandedState() {
-        if (mBubbleTaskViewHelper != null) {
-            if (mTaskView != null) {
-                removeView(mTaskView);
-            }
-        }
         mMenuViewController.hideMenu(false /* animated */);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index bd8ce80..b95d258 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -34,7 +34,9 @@
 import android.widget.FrameLayout;
 
 import com.android.wm.shell.R;
+import com.android.wm.shell.bubbles.Bubble;
 import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.bubbles.BubbleData;
 import com.android.wm.shell.bubbles.BubbleOverflow;
 import com.android.wm.shell.bubbles.BubblePositioner;
 import com.android.wm.shell.bubbles.BubbleViewProvider;
@@ -61,6 +63,7 @@
     private static final float SCRIM_ALPHA = 0.2f;
 
     private final BubbleController mBubbleController;
+    private final BubbleData mBubbleData;
     private final BubblePositioner mPositioner;
     private final BubbleBarAnimationHelper mAnimationHelper;
     private final BubbleEducationViewController mEducationViewController;
@@ -85,9 +88,10 @@
     private TouchDelegate mHandleTouchDelegate;
     private final Rect mHandleTouchBounds = new Rect();
 
-    public BubbleBarLayerView(Context context, BubbleController controller) {
+    public BubbleBarLayerView(Context context, BubbleController controller, BubbleData bubbleData) {
         super(context);
         mBubbleController = controller;
+        mBubbleData = bubbleData;
         mPositioner = mBubbleController.getPositioner();
 
         mAnimationHelper = new BubbleBarAnimationHelper(context,
@@ -236,15 +240,44 @@
         showScrim(true);
     }
 
+    /** Removes the given {@code bubble}. */
+    public void removeBubble(Bubble bubble) {
+        if (mBubbleData.getBubbles().isEmpty()) {
+            // we're removing the last bubble. collapse the expanded view and cleanup bubble views
+            // at the end.
+            collapse(bubble::cleanupViews);
+        } else {
+            bubble.cleanupViews();
+        }
+    }
+
     /** Collapses any showing expanded view */
     public void collapse() {
+        collapse(/* endAction= */ null);
+    }
+
+    /**
+     * Collapses any showing expanded view.
+     *
+     * @param endAction an action to run and the end of the collapse animation.
+     */
+    public void collapse(@Nullable Runnable endAction) {
+        if (!mIsExpanded) {
+            return;
+        }
         mIsExpanded = false;
         final BubbleBarExpandedView viewToRemove = mExpandedView;
         mEducationViewController.hideEducation(/* animated = */ true);
+        Runnable runnable = () -> {
+            removeView(viewToRemove);
+            if (endAction != null) {
+                endAction.run();
+            }
+        };
         if (mDragController != null && mDragController.isStuckToDismiss()) {
-            mAnimationHelper.animateDismiss(() -> removeView(viewToRemove));
+            mAnimationHelper.animateDismiss(runnable);
         } else {
-            mAnimationHelper.animateCollapse(() -> removeView(viewToRemove));
+            mAnimationHelper.animateCollapse(runnable);
         }
         mBubbleController.getSysuiProxy().onStackExpandChanged(false);
         mExpandedView = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
new file mode 100644
index 0000000..fd91ac0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.wm.shell.desktopmode
+
+import android.window.WindowContainerTransaction
+import com.android.wm.shell.sysui.ShellCommandHandler
+import java.io.PrintWriter
+
+/**
+ * Handles the shell commands for the DesktopTasksController.
+ */
+class DesktopModeShellCommandHandler(private val controller: DesktopTasksController) :
+    ShellCommandHandler.ShellCommandActionHandler {
+
+    override fun onShellCommand(args: Array<String>, pw: PrintWriter): Boolean {
+        return when (args[0]) {
+            "moveToDesktop" -> {
+                if (!runMoveToDesktop(args, pw)) {
+                    pw.println("Task not found. Please enter a valid taskId.")
+                    false
+                } else {
+                    true
+                }
+            }
+
+            else -> {
+                pw.println("Invalid command: ${args[0]}")
+                false
+            }
+        }
+    }
+
+    private fun runMoveToDesktop(args: Array<String>, pw: PrintWriter): Boolean {
+        if (args.size < 2) {
+            // First argument is the action name.
+            pw.println("Error: task id should be provided as arguments")
+            return false
+        }
+
+        val taskId = try {
+            args[1].toInt()
+        } catch (e: NumberFormatException) {
+            pw.println("Error: task id should be an integer")
+            return false
+        }
+
+        return controller.moveToDesktopWithoutDecor(taskId, WindowContainerTransaction())
+    }
+
+    override fun printShellCommandHelp(pw: PrintWriter, prefix: String) {
+        pw.println("$prefix moveToDesktop <taskId> ")
+        pw.println("$prefix  Move a task with given id to desktop mode.")
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index a089e81..e872849 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -100,6 +100,9 @@
 
     private val desktopMode: DesktopModeImpl
     private var visualIndicator: DesktopModeVisualIndicator? = null
+    private val desktopModeShellCommandHandler: DesktopModeShellCommandHandler =
+        DesktopModeShellCommandHandler(this)
+
     private val mOnAnimationFinishedCallback = Consumer<SurfaceControl.Transaction> {
         t: SurfaceControl.Transaction ->
         visualIndicator?.releaseVisualIndicator(t)
@@ -148,6 +151,8 @@
     private fun onInit() {
         KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController")
         shellCommandHandler.addDumpCallback(this::dump, this)
+        shellCommandHandler.addCommandCallback("desktopmode", desktopModeShellCommandHandler,
+            this)
         shellController.addExternalInterface(
             ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE,
             { createExternalInterface() },
@@ -240,6 +245,40 @@
         }
     }
 
+    /** Move a task with given `taskId` to desktop without decor */
+    fun moveToDesktopWithoutDecor(
+        taskId: Int,
+        wct: WindowContainerTransaction
+    ): Boolean {
+        val task = shellTaskOrganizer.getRunningTaskInfo(taskId) ?: return false
+        moveToDesktopWithoutDecor(task, wct)
+        return true
+    }
+
+    /**
+     * Move a task to desktop without decor
+     */
+    private fun moveToDesktopWithoutDecor(
+        task: RunningTaskInfo,
+        wct: WindowContainerTransaction
+    ) {
+        KtProtoLog.v(
+            WM_SHELL_DESKTOP_MODE,
+            "DesktopTasksController: moveToDesktopWithoutDecor taskId=%d",
+            task.taskId
+        )
+        exitSplitIfApplicable(wct, task)
+        // Bring other apps to front first
+        bringDesktopAppsToFront(task.displayId, wct)
+        addMoveToDesktopChanges(wct, task)
+
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
+        } else {
+            shellTaskOrganizer.applyTransaction(wct)
+        }
+    }
+
     /**
      * Move a task to desktop
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
index 99df6a3..0c25f27 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
@@ -18,6 +18,7 @@
 
 import android.internal.perfetto.protos.PerfettoTrace;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.tracing.perfetto.DataSourceInstance;
 import android.tracing.perfetto.DataSourceParams;
 import android.tracing.perfetto.InitArguments;
@@ -58,6 +59,15 @@
             return;
         }
 
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logDispatched");
+        try {
+            doLogDispatched(transitionId, handler);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+        }
+    }
+
+    private void doLogDispatched(int transitionId, Transitions.TransitionHandler handler) {
         mDataSource.trace(ctx -> {
             final int handlerId = getHandlerId(handler, ctx);
 
@@ -97,6 +107,15 @@
             return;
         }
 
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logMergeRequested");
+        try {
+            doLogMergeRequested(mergeRequestedTransitionId, playingTransitionId);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+        }
+    }
+
+    private void doLogMergeRequested(int mergeRequestedTransitionId, int playingTransitionId) {
         mDataSource.trace(ctx -> {
             final ProtoOutputStream os = ctx.newTracePacket();
             final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
@@ -120,10 +139,19 @@
             return;
         }
 
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logMerged");
+        try {
+            doLogMerged(mergedTransitionId, playingTransitionId);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+        }
+    }
+
+    private void doLogMerged(int mergeRequestedTransitionId, int playingTransitionId) {
         mDataSource.trace(ctx -> {
             final ProtoOutputStream os = ctx.newTracePacket();
             final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
-            os.write(PerfettoTrace.ShellTransition.ID, mergedTransitionId);
+            os.write(PerfettoTrace.ShellTransition.ID, mergeRequestedTransitionId);
             os.write(PerfettoTrace.ShellTransition.MERGE_TIME_NS,
                     SystemClock.elapsedRealtimeNanos());
             os.write(PerfettoTrace.ShellTransition.MERGE_TARGET, playingTransitionId);
@@ -142,6 +170,15 @@
             return;
         }
 
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logAborted");
+        try {
+            doLogAborted(transitionId);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+        }
+    }
+
+    private void doLogAborted(int transitionId) {
         mDataSource.trace(ctx -> {
             final ProtoOutputStream os = ctx.newTracePacket();
             final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
@@ -157,6 +194,15 @@
     }
 
     private void onFlush() {
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "onFlush");
+        try {
+            doOnFlush();
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+        }
+    }
+
+    private void doOnFlush() {
         mDataSource.trace(ctx -> {
             final ProtoOutputStream os = ctx.newTracePacket();
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index a8b39c41..891eea0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -888,7 +888,10 @@
     private DesktopModeWindowDecoration getFocusedDecor() {
         final int size = mWindowDecorByTaskId.size();
         DesktopModeWindowDecoration focusedDecor = null;
-        for (int i = 0; i < size; i++) {
+        // TODO(b/323251951): We need to iterate this in reverse to avoid potentially getting
+        //  a decor for a closed task. This is a short term fix while the core issue is addressed,
+        //  which involves refactoring the window decor lifecycle to be visibility based.
+        for (int i = size - 1; i >= 0; i--) {
             final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
             if (decor != null && decor.isFocused()) {
                 focusedDecor = decor;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
index 75965d6..1668e37 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
@@ -143,7 +143,7 @@
                 bubbleController,
                 mainExecutor
             )
-        bubbleBarLayerView = BubbleBarLayerView(context, bubbleController)
+        bubbleBarLayerView = BubbleBarLayerView(context, bubbleController, bubbleData)
     }
 
     @Test
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index 295f4dc..e5bdeee 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -763,41 +763,41 @@
 }; // namespace CanvasJNI
 
 static const JNINativeMethod gMethods[] = {
-    {"nGetNativeFinalizer", "()J", (void*) CanvasJNI::getNativeFinalizer},
-    {"nFreeCaches", "()V", (void*) CanvasJNI::freeCaches},
-    {"nFreeTextLayoutCaches", "()V", (void*) CanvasJNI::freeTextLayoutCaches},
-    {"nSetCompatibilityVersion", "(I)V", (void*) CanvasJNI::setCompatibilityVersion},
+        {"nGetNativeFinalizer", "()J", (void*)CanvasJNI::getNativeFinalizer},
+        {"nFreeCaches", "()V", (void*)CanvasJNI::freeCaches},
+        {"nFreeTextLayoutCaches", "()V", (void*)CanvasJNI::freeTextLayoutCaches},
+        {"nSetCompatibilityVersion", "(I)V", (void*)CanvasJNI::setCompatibilityVersion},
 
-    // ------------ @FastNative ----------------
-    {"nInitRaster", "(J)J", (void*) CanvasJNI::initRaster},
-    {"nSetBitmap", "(JJ)V", (void*) CanvasJNI::setBitmap},
-    {"nGetClipBounds","(JLandroid/graphics/Rect;)Z", (void*) CanvasJNI::getClipBounds},
+        // ------------ @FastNative ----------------
+        {"nInitRaster", "(J)J", (void*)CanvasJNI::initRaster},
+        {"nSetBitmap", "(JJ)V", (void*)CanvasJNI::setBitmap},
+        {"nGetClipBounds", "(JLandroid/graphics/Rect;)Z", (void*)CanvasJNI::getClipBounds},
 
-    // ------------ @CriticalNative ----------------
-    {"nIsOpaque","(J)Z", (void*) CanvasJNI::isOpaque},
-    {"nGetWidth","(J)I", (void*) CanvasJNI::getWidth},
-    {"nGetHeight","(J)I", (void*) CanvasJNI::getHeight},
-    {"nSave","(JI)I", (void*) CanvasJNI::save},
-    {"nSaveLayer","(JFFFFJ)I", (void*) CanvasJNI::saveLayer},
-    {"nSaveLayerAlpha","(JFFFFI)I", (void*) CanvasJNI::saveLayerAlpha},
-    {"nSaveUnclippedLayer","(JIIII)I", (void*) CanvasJNI::saveUnclippedLayer},
-    {"nRestoreUnclippedLayer","(JIJ)V", (void*) CanvasJNI::restoreUnclippedLayer},
-    {"nGetSaveCount","(J)I", (void*) CanvasJNI::getSaveCount},
-    {"nRestore","(J)Z", (void*) CanvasJNI::restore},
-    {"nRestoreToCount","(JI)V", (void*) CanvasJNI::restoreToCount},
-    {"nGetMatrix", "(JJ)V", (void*)CanvasJNI::getMatrix},
-    {"nSetMatrix","(JJ)V", (void*) CanvasJNI::setMatrix},
-    {"nConcat","(JJ)V", (void*) CanvasJNI::concat},
-    {"nConcat","(J[F)V", (void*) CanvasJNI::concat44},
-    {"nRotate","(JF)V", (void*) CanvasJNI::rotate},
-    {"nScale","(JFF)V", (void*) CanvasJNI::scale},
-    {"nSkew","(JFF)V", (void*) CanvasJNI::skew},
-    {"nTranslate","(JFF)V", (void*) CanvasJNI::translate},
-    {"nQuickReject","(JJ)Z", (void*) CanvasJNI::quickRejectPath},
-    {"nQuickReject","(JFFFF)Z", (void*)CanvasJNI::quickRejectRect},
-    {"nClipRect","(JFFFFI)Z", (void*) CanvasJNI::clipRect},
-    {"nClipPath","(JJI)Z", (void*) CanvasJNI::clipPath},
-    {"nSetDrawFilter", "(JJ)V", (void*) CanvasJNI::setPaintFilter},
+        // ------------ @CriticalNative ----------------
+        {"nIsOpaque", "(J)Z", (void*)CanvasJNI::isOpaque},
+        {"nGetWidth", "(J)I", (void*)CanvasJNI::getWidth},
+        {"nGetHeight", "(J)I", (void*)CanvasJNI::getHeight},
+        {"nSave", "(JI)I", (void*)CanvasJNI::save},
+        {"nSaveLayer", "(JFFFFJ)I", (void*)CanvasJNI::saveLayer},
+        {"nSaveLayerAlpha", "(JFFFFI)I", (void*)CanvasJNI::saveLayerAlpha},
+        {"nSaveUnclippedLayer", "(JIIII)I", (void*)CanvasJNI::saveUnclippedLayer},
+        {"nRestoreUnclippedLayer", "(JIJ)V", (void*)CanvasJNI::restoreUnclippedLayer},
+        {"nGetSaveCount", "(J)I", (void*)CanvasJNI::getSaveCount},
+        {"nRestore", "(J)Z", (void*)CanvasJNI::restore},
+        {"nRestoreToCount", "(JI)V", (void*)CanvasJNI::restoreToCount},
+        {"nGetMatrix", "(JJ)V", (void*)CanvasJNI::getMatrix},
+        {"nSetMatrix", "(JJ)V", (void*)CanvasJNI::setMatrix},
+        {"nConcat", "(JJ)V", (void*)CanvasJNI::concat},
+        {"nConcat", "(J[F)V", (void*)CanvasJNI::concat44},
+        {"nRotate", "(JF)V", (void*)CanvasJNI::rotate},
+        {"nScale", "(JFF)V", (void*)CanvasJNI::scale},
+        {"nSkew", "(JFF)V", (void*)CanvasJNI::skew},
+        {"nTranslate", "(JFF)V", (void*)CanvasJNI::translate},
+        {"nQuickReject", "(JJ)Z", (void*)CanvasJNI::quickRejectPath},
+        {"nQuickReject", "(JFFFF)Z", (void*)CanvasJNI::quickRejectRect},
+        {"nClipRect", "(JFFFFI)Z", (void*)CanvasJNI::clipRect},
+        {"nClipPath", "(JJI)Z", (void*)CanvasJNI::clipPath},
+        {"nSetDrawFilter", "(JJ)V", (void*)CanvasJNI::setPaintFilter},
 };
 
 // If called from Canvas these are regular JNI
diff --git a/media/java/android/media/BluetoothProfileConnectionInfo.java b/media/java/android/media/BluetoothProfileConnectionInfo.java
index e4dc152..0613fc6 100644
--- a/media/java/android/media/BluetoothProfileConnectionInfo.java
+++ b/media/java/android/media/BluetoothProfileConnectionInfo.java
@@ -15,6 +15,9 @@
  */
 package android.media;
 
+import static android.media.audio.Flags.FLAG_SCO_MANAGED_BY_AUDIO;
+
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.bluetooth.BluetoothProfile;
@@ -174,4 +177,13 @@
     public boolean isLeOutput() {
         return mIsLeOutput;
     }
+
+    /**
+     * Factory method for <code>BluetoothProfileConnectionInfo</code> for an HFP device.
+     */
+    @FlaggedApi(FLAG_SCO_MANAGED_BY_AUDIO)
+    public static @NonNull BluetoothProfileConnectionInfo createHfpInfo() {
+        return new BluetoothProfileConnectionInfo(BluetoothProfile.HEADSET, false,
+                -1, false);
+    }
 }
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 1046d8e9..9742d46 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -64,10 +64,8 @@
   }
 
   public final class NfcAdapter {
-    method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean allowTransaction();
     method public void disableForegroundDispatch(android.app.Activity);
     method public void disableReaderMode(android.app.Activity);
-    method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean disallowTransaction();
     method public void enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]);
     method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle);
     method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context);
@@ -83,6 +81,7 @@
     method @FlaggedApi("android.nfc.enable_nfc_charging") public boolean isWlcEnabled();
     method @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public void resetDiscoveryTechnology(@NonNull android.app.Activity);
     method @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public void setDiscoveryTechnology(@NonNull android.app.Activity, int, int);
+    method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setTransactionAllowed(boolean);
     field public static final String ACTION_ADAPTER_STATE_CHANGED = "android.nfc.action.ADAPTER_STATE_CHANGED";
     field public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
     field @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static final String ACTION_PREFERRED_PAYMENT_CHANGED = "android.nfc.action.PREFERRED_PAYMENT_CHANGED";
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 4d56c11..55506a1 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -1204,37 +1204,21 @@
         }
     }
 
-   /**
-    * Disables observe mode to allow the transaction to proceed. See
-    * {@link #isObserveModeSupported()} for a description of observe mode and
-    * use {@link #disallowTransaction()} to enable observe mode and block
-    * transactions again.
-    *
-    * @return boolean indicating success or failure.
-    */
-    @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
-    public boolean allowTransaction() {
-        try {
-            return sService.setObserveMode(false);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            return false;
-        }
-    }
-
     /**
-    * Signals that the transaction has completed and observe mode may be
-    * reenabled. See {@link #isObserveModeSupported()} for a description of
-    * observe mode and use {@link #allowTransaction()} to disable observe
-    * mode and allow transactions to proceed.
-    *
-    * @return boolean indicating success or failure.
-    */
+     * Controls whether the NFC adapter will allow transactions to proceed or be in observe mode
+     * and simply observe and notify the APDU service of polling loop frames. See
+     * {@link #isObserveModeSupported()} for a description of observe mode.
+     *
+     * @param allowed true disables observe mode to allow the transaction to proceed while false
+     *                enables observe mode and does not allow transactions to proceed.
+     *
+     * @return boolean indicating success or failure.
+     */
 
     @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
-    public boolean disallowTransaction() {
+    public boolean setTransactionAllowed(boolean allowed) {
         try {
-            return sService.setObserveMode(true);
+            return sService.setObserveMode(!allowed);
         } catch (RemoteException e) {
             attemptDeadServiceRecovery(e);
             return false;
diff --git a/opengl/java/android/opengl/EGLExt.java b/opengl/java/android/opengl/EGLExt.java
index 1570e0e..31104a0 100644
--- a/opengl/java/android/opengl/EGLExt.java
+++ b/opengl/java/android/opengl/EGLExt.java
@@ -46,14 +46,6 @@
         _nativeClassInit();
     }
 
-    // C function EGLBoolean eglPresentationTimeANDROID ( EGLDisplay dpy, EGLSurface sur, EGLnsecsANDROID time )
-
-    public static native boolean eglPresentationTimeANDROID(
-        EGLDisplay dpy,
-        EGLSurface sur,
-        long time
-    );
-
     /**
      * Retrieves the SyncFence for an EGLSync created with EGL_SYNC_NATIVE_FENCE_ANDROID
      *
@@ -83,4 +75,13 @@
     }
 
     private static native int eglDupNativeFenceFDANDROIDImpl(EGLDisplay display, EGLSync sync);
+
+    // C function EGLBoolean eglPresentationTimeANDROID ( EGLDisplay dpy, EGLSurface sur, EGLnsecsANDROID time )
+
+    public static native boolean eglPresentationTimeANDROID(
+        EGLDisplay dpy,
+        EGLSurface sur,
+        long time
+    );
+
 }
diff --git a/packages/CrashRecovery/services/Android.bp b/packages/CrashRecovery/services/Android.bp
index 27ddff9..63e6c50 100644
--- a/packages/CrashRecovery/services/Android.bp
+++ b/packages/CrashRecovery/services/Android.bp
@@ -3,7 +3,15 @@
     srcs: [
         "java/**/*.java",
         "java/**/*.aidl",
+        ":statslog-crashrecovery-java-gen",
     ],
-    path: "java",
     visibility: ["//frameworks/base:__subpackages__"],
 }
+
+genrule {
+    name: "statslog-crashrecovery-java-gen",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --java $(out) --module crashrecovery " +
+        "--javaPackage com.android.server.crashrecovery.proto --javaClass CrashRecoveryStatsLog --worksource",
+    out: ["com/android/server/crashrecovery/proto/CrashRecoveryStatsLog.java"],
+}
diff --git a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
index dd54334..dffe4e2 100644
--- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
@@ -46,11 +46,11 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.PackageWatchdog.FailureReasons;
 import com.android.server.PackageWatchdog.PackageHealthObserver;
 import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
 import com.android.server.am.SettingsToPropertiesMapper;
+import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -390,7 +390,7 @@
             return;
         }
 
-        FrameworkStatsLog.write(FrameworkStatsLog.RESCUE_PARTY_RESET_REPORTED, level);
+        CrashRecoveryStatsLog.write(CrashRecoveryStatsLog.RESCUE_PARTY_RESET_REPORTED, level);
         // Try our best to reset all settings possible, and once finished
         // rethrow any exception that we encountered
         Exception res = null;
diff --git a/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 2007079..50322f0 100644
--- a/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -39,13 +39,13 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.Preconditions;
 import com.android.server.PackageWatchdog;
 import com.android.server.PackageWatchdog.FailureReasons;
 import com.android.server.PackageWatchdog.PackageHealthObserver;
 import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
 import com.android.server.SystemConfig;
+import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog;
 import com.android.server.pm.ApexManager;
 
 import java.io.BufferedReader;
@@ -418,7 +418,7 @@
 
         final VersionedPackage logPackage = logPackageTemp;
         WatchdogRollbackLogger.logEvent(logPackage,
-                FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE,
+                CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE,
                 reasonToLog, failedPackageToLog);
 
         Consumer<Intent> onResult = result -> {
@@ -430,19 +430,19 @@
                     int rollbackId = rollback.getRollbackId();
                     saveStagedRollbackId(rollbackId, logPackage);
                     WatchdogRollbackLogger.logEvent(logPackage,
-                            FrameworkStatsLog
+                            CrashRecoveryStatsLog
                             .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED,
                             reasonToLog, failedPackageToLog);
 
                 } else {
                     WatchdogRollbackLogger.logEvent(logPackage,
-                            FrameworkStatsLog
+                            CrashRecoveryStatsLog
                                     .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS,
                             reasonToLog, failedPackageToLog);
                 }
             } else {
                 WatchdogRollbackLogger.logEvent(logPackage,
-                        FrameworkStatsLog
+                        CrashRecoveryStatsLog
                                 .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
                         reasonToLog, failedPackageToLog);
             }
diff --git a/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java b/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java
index f9ef994..898c543 100644
--- a/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java
+++ b/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java
@@ -16,16 +16,16 @@
 
 package com.android.server.rollback;
 
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -42,8 +42,8 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.PackageWatchdog;
+import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog;
 
 import java.util.List;
 import java.util.Set;
@@ -197,8 +197,8 @@
                 + " rollbackReason: " + rollbackReasonToString(rollbackReason)
                 + " failedPackageName: " + failingPackageName);
         if (logPackage != null) {
-            FrameworkStatsLog.write(
-                    FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED,
+            CrashRecoveryStatsLog.write(
+                    CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED,
                     type,
                     logPackage.getPackageName(),
                     logPackage.getVersionCode(),
@@ -208,8 +208,8 @@
         } else {
             // In the case that the log package is null, still log an empty string as an
             // indication that retrieving the logging parent failed.
-            FrameworkStatsLog.write(
-                    FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED,
+            CrashRecoveryStatsLog.write(
+                    CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED,
                     type,
                     "",
                     0,
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index f4641b9..f425f52 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -272,7 +272,7 @@
     <!-- The title of a dialog which asks the user to restore (i.e. re-install, re-download) an app
          after parts of the app have been previously moved into the cloud for temporary storage.
          "installername" is the app that will facilitate the download of the app. [CHAR LIMIT=50] -->
-    <string name="unarchive_application_title">Restore <xliff:g id="appname" example="Bird Game">%1$s</xliff:g> from <xliff:g id="installername" example="App Store">%1$s</xliff:g>?</string>
+    <string name="unarchive_application_title">Restore <xliff:g id="appname" example="Bird Game">%1$s</xliff:g> from <xliff:g id="installername" example="App Store">%2$s</xliff:g>?</string>
     <!-- After the user confirms the dialog, a download will start. [CHAR LIMIT=none] -->
     <string name="unarchive_body_text">This app will begin to download in the background</string>
     <!-- The action to restore (i.e. re-install, re-download) an app after parts of the app have been previously moved
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java
index 9af799c..b20117d 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java
@@ -25,10 +25,12 @@
 import android.app.Fragment;
 import android.app.FragmentTransaction;
 import android.content.IntentSender;
+import android.content.pm.InstallSourceInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.os.Process;
+import android.text.TextUtils;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
@@ -97,14 +99,30 @@
             String appTitle = pm.getApplicationInfo(mPackageName,
                     PackageManager.ApplicationInfoFlags.of(
                             MATCH_ARCHIVED_PACKAGES)).loadLabel(pm).toString();
-            // TODO(ag/25387215) Get the real installer title here after fixing getInstallSource for
-            //  archived apps.
-            showDialogFragment(appTitle, "installerTitle");
+            String installerTitle = getResponsibleInstallerTitle(pm,
+                    pm.getInstallSourceInfo(mPackageName));
+            showDialogFragment(appTitle, installerTitle);
         } catch (PackageManager.NameNotFoundException e) {
             Log.e(TAG, "Invalid packageName: " + e.getMessage());
         }
     }
 
+    private String getResponsibleInstallerTitle(PackageManager pm,
+            InstallSourceInfo installSource)
+            throws PackageManager.NameNotFoundException {
+        String packageName = TextUtils.isEmpty(installSource.getUpdateOwnerPackageName())
+                ? installSource.getInstallingPackageName()
+                : installSource.getUpdateOwnerPackageName();
+        if (packageName == null) {
+            // Should be unreachable.
+            Log.e(TAG, "Installer not found.");
+            setResult(Activity.RESULT_FIRST_USER);
+            finish();
+            return "";
+        }
+        return pm.getApplicationInfo(packageName, /* flags= */ 0).loadLabel(pm).toString();
+    }
+
     @NonNull
     private String[] getRequestedPermissions(String callingPackage) {
         String[] requestedPermissions = null;
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index 3355fb3..6761aa7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -16,29 +16,71 @@
 
 package com.android.settingslib.volume.data.repository
 
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.media.AudioDeviceInfo
 import android.media.AudioManager
+import android.media.AudioManager.OnCommunicationDeviceChangedListener
+import androidx.concurrent.futures.DirectExecutor
 import com.android.internal.util.ConcurrentUtils
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.settingslib.volume.shared.model.AudioStreamModel
+import com.android.settingslib.volume.shared.model.RingerMode
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 
-/** Provides audio managing functionality and data. */
+/** Provides audio streams state and managing functionality. */
 interface AudioRepository {
 
     /** Current [AudioManager.getMode]. */
     val mode: StateFlow<Int>
+
+    /**
+     * Ringtone mode.
+     *
+     * @see AudioManager.getRingerModeInternal
+     */
+    val ringerMode: StateFlow<RingerMode>
+
+    /**
+     * Communication device. Emits null when there is no communication device available.
+     *
+     * @see AudioDeviceInfo.getType
+     */
+    val communicationDevice: StateFlow<AudioDeviceInfo?>
+
+    /** State of the [AudioStream]. */
+    suspend fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel>
+
+    /** Current state of the [AudioStream]. */
+    suspend fun getCurrentAudioStream(audioStream: AudioStream): AudioStreamModel
+
+    suspend fun setVolume(audioStream: AudioStream, volume: Int)
+
+    suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean)
 }
 
 class AudioRepositoryImpl(
+    private val context: Context,
     private val audioManager: AudioManager,
-    backgroundCoroutineContext: CoroutineContext,
-    coroutineScope: CoroutineScope,
+    private val backgroundCoroutineContext: CoroutineContext,
+    private val coroutineScope: CoroutineScope,
 ) : AudioRepository {
 
     override val mode: StateFlow<Int> =
@@ -50,4 +92,117 @@
             }
             .flowOn(backgroundCoroutineContext)
             .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), audioManager.mode)
+
+    private val audioManagerIntents: SharedFlow<String> =
+        callbackFlow {
+                val receiver =
+                    object : BroadcastReceiver() {
+                        override fun onReceive(context: Context?, intent: Intent) {
+                            intent.action?.let { action -> launch { send(action) } }
+                        }
+                    }
+                context.registerReceiver(
+                    receiver,
+                    IntentFilter().apply {
+                        for (action in allActions) {
+                            addAction(action)
+                        }
+                    }
+                )
+
+                awaitClose { context.unregisterReceiver(receiver) }
+            }
+            .shareIn(coroutineScope, SharingStarted.WhileSubscribed())
+
+    override val ringerMode: StateFlow<RingerMode> =
+        audioManagerIntents
+            .filter { ringerActions.contains(it) }
+            .map { RingerMode(audioManager.ringerModeInternal) }
+            .flowOn(backgroundCoroutineContext)
+            .stateIn(
+                coroutineScope,
+                SharingStarted.WhileSubscribed(),
+                RingerMode(audioManager.ringerModeInternal),
+            )
+
+    override val communicationDevice: StateFlow<AudioDeviceInfo?>
+        get() =
+            callbackFlow {
+                    val listener = OnCommunicationDeviceChangedListener { trySend(Unit) }
+                    audioManager.addOnCommunicationDeviceChangedListener(
+                        DirectExecutor.INSTANCE,
+                        listener
+                    )
+
+                    awaitClose { audioManager.removeOnCommunicationDeviceChangedListener(listener) }
+                }
+                .filterNotNull()
+                .map { audioManager.communicationDevice }
+                .flowOn(backgroundCoroutineContext)
+                .stateIn(
+                    coroutineScope,
+                    SharingStarted.WhileSubscribed(),
+                    audioManager.communicationDevice,
+                )
+
+    override suspend fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> {
+        return audioManagerIntents
+            .filter { modelActions.contains(it) }
+            .map { getCurrentAudioStream(audioStream) }
+            .flowOn(backgroundCoroutineContext)
+    }
+
+    override suspend fun getCurrentAudioStream(audioStream: AudioStream): AudioStreamModel {
+        return withContext(backgroundCoroutineContext) {
+            AudioStreamModel(
+                audioStream = audioStream,
+                minVolume = getMinVolume(audioStream),
+                maxVolume = audioManager.getStreamMaxVolume(audioStream.value),
+                volume = audioManager.getStreamVolume(audioStream.value),
+                isAffectedByRingerMode =
+                    audioManager.isStreamAffectedByRingerMode(audioStream.value),
+                isMuted = audioManager.isStreamMute(audioStream.value)
+            )
+        }
+    }
+
+    override suspend fun setVolume(audioStream: AudioStream, volume: Int) =
+        withContext(backgroundCoroutineContext) {
+            audioManager.setStreamVolume(audioStream.value, volume, 0)
+        }
+
+    override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) =
+        withContext(backgroundCoroutineContext) {
+            if (isMuted) {
+                audioManager.adjustStreamVolume(audioStream.value, 0, AudioManager.ADJUST_MUTE)
+            } else {
+                audioManager.adjustStreamVolume(audioStream.value, 0, AudioManager.ADJUST_UNMUTE)
+            }
+        }
+
+    private fun getMinVolume(stream: AudioStream): Int =
+        try {
+            audioManager.getStreamMinVolume(stream.value)
+        } catch (e: IllegalArgumentException) {
+            // Fallback to STREAM_VOICE_CALL because
+            // CallVolumePreferenceController.java default
+            // return STREAM_VOICE_CALL in getAudioStream
+            audioManager.getStreamMinVolume(AudioManager.STREAM_VOICE_CALL)
+        }
+
+    private companion object {
+        val modelActions =
+            setOf(
+                AudioManager.STREAM_MUTE_CHANGED_ACTION,
+                AudioManager.MASTER_MUTE_CHANGED_ACTION,
+                AudioManager.VOLUME_CHANGED_ACTION,
+                AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
+                AudioManager.STREAM_DEVICES_CHANGED_ACTION,
+            )
+        val ringerActions =
+            setOf(
+                AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
+            )
+        val allActions = ringerActions + modelActions
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSystemRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSystemRepository.kt
new file mode 100644
index 0000000..2b12936
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSystemRepository.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.settingslib.volume.data.repository
+
+import android.content.Context
+import android.media.AudioSystem
+
+/** Provides the current state of the audio system. */
+interface AudioSystemRepository {
+
+    val isSingleVolume: Boolean
+}
+
+class AudioSystemRepositoryImpl(private val context: Context) : AudioSystemRepository {
+
+    override val isSingleVolume: Boolean
+        get() = AudioSystem.isSingleVolume(context)
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt
new file mode 100644
index 0000000..58f3c2d
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.settingslib.volume.shared.model
+
+import android.media.AudioManager
+
+/** Type-safe wrapper for [AudioManager] audio stream. */
+@JvmInline
+value class AudioStream(val value: Int) {
+    init {
+        require(value in supportedStreamTypes) { "Unsupported stream=$value" }
+    }
+
+    private companion object {
+        val supportedStreamTypes =
+            setOf(
+                AudioManager.STREAM_VOICE_CALL,
+                AudioManager.STREAM_SYSTEM,
+                AudioManager.STREAM_RING,
+                AudioManager.STREAM_MUSIC,
+                AudioManager.STREAM_ALARM,
+                AudioManager.STREAM_NOTIFICATION,
+                AudioManager.STREAM_BLUETOOTH_SCO,
+                AudioManager.STREAM_SYSTEM_ENFORCED,
+                AudioManager.STREAM_DTMF,
+                AudioManager.STREAM_TTS,
+                AudioManager.STREAM_ACCESSIBILITY,
+                AudioManager.STREAM_ASSISTANT,
+            )
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStreamModel.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStreamModel.kt
new file mode 100644
index 0000000..c1be1ee
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStreamModel.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.settingslib.volume.shared.model
+
+/** Current state of the audio stream. */
+data class AudioStreamModel(
+    val audioStream: AudioStream,
+    val volume: Int,
+    val minVolume: Int,
+    val maxVolume: Int,
+    val isAffectedByRingerMode: Boolean,
+    val isMuted: Boolean,
+)
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/RingerMode.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/RingerMode.kt
new file mode 100644
index 0000000..9f03927
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/RingerMode.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.settingslib.volume.shared.model
+
+import android.media.AudioManager
+
+/** Type-safe wrapper for [AudioManager] ringer mode. */
+@JvmInline
+value class RingerMode(val value: Int) {
+
+    init {
+        require(value in supportedRingerModes) { "Unsupported stream=$value" }
+    }
+
+    private companion object {
+        val supportedRingerModes =
+            setOf(
+                AudioManager.RINGER_MODE_SILENT,
+                AudioManager.RINGER_MODE_VIBRATE,
+                AudioManager.RINGER_MODE_NORMAL,
+                AudioManager.RINGER_MODE_MAX,
+            )
+    }
+}
diff --git a/packages/SettingsLib/tests/integ/Android.bp b/packages/SettingsLib/tests/integ/Android.bp
index ce3a7ba..f303ab5 100644
--- a/packages/SettingsLib/tests/integ/Android.bp
+++ b/packages/SettingsLib/tests/integ/Android.bp
@@ -40,6 +40,8 @@
         "android.test.runner",
         "telephony-common",
         "android.test.base",
+        "android.test.mock",
+        "truth",
     ],
 
     platform_apis: true,
@@ -49,16 +51,23 @@
         "androidx.test.core",
         "androidx.test.rules",
         "androidx.test.espresso.core",
+        "androidx.test.ext.junit",
         "flag-junit",
-        "mockito-target-minus-junit4",
+        "kotlinx_coroutines_test",
+        "mockito-target-extended-minus-junit4",
         "platform-test-annotations",
         "truth",
         "SettingsLibDeviceStateRotationLock",
         "SettingsLibSettingsSpinner",
         "SettingsLibUsageProgressBarPreference",
         "settingslib_media_flags_lib",
-        "kotlinx_coroutines_test",
     ],
 
+    jni_libs: [
+        "libdexmakerjvmtiagent",
+        "libmultiplejvmtiagentsinterferenceagent",
+        "libstaticjvmtiagent",
+    ],
     dxflags: ["--multi-dex"],
+    manifest: "AndroidManifest.xml",
 }
diff --git a/packages/SettingsLib/tests/integ/AndroidManifest.xml b/packages/SettingsLib/tests/integ/AndroidManifest.xml
index 32048ca..9fb1c1f 100644
--- a/packages/SettingsLib/tests/integ/AndroidManifest.xml
+++ b/packages/SettingsLib/tests/integ/AndroidManifest.xml
@@ -25,7 +25,7 @@
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
 
-    <application>
+    <application android:debuggable="true" android:testOnly="true">
         <uses-library android:name="android.test.runner" />
         <activity android:name=".drawer.SettingsDrawerActivityTest$TestActivity"/>
 
diff --git a/packages/SettingsLib/tests/integ/AndroidTest.xml b/packages/SettingsLib/tests/integ/AndroidTest.xml
index d0aee88..9de6019 100644
--- a/packages/SettingsLib/tests/integ/AndroidTest.xml
+++ b/packages/SettingsLib/tests/integ/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Runs Tests for SettingsLib.">
     <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
         <option name="test-file-name" value="SettingsLibTests.apk" />
+        <option name="install-arg" value="-t" />
     </target_preparer>
 
     <option name="test-suite-tag" value="apct" />
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
new file mode 100644
index 0000000..7b70c64
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.volume.data.repository
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.media.AudioDeviceInfo
+import android.media.AudioManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.settingslib.volume.shared.model.AudioStreamModel
+import com.android.settingslib.volume.shared.model.RingerMode
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@Suppress("UnspecifiedRegisterReceiverFlag")
+@RunWith(AndroidJUnit4::class)
+class AudioRepositoryTest {
+
+    @Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
+    @Captor
+    private lateinit var modeListenerCaptor: ArgumentCaptor<AudioManager.OnModeChangedListener>
+    @Captor
+    private lateinit var communicationDeviceListenerCaptor:
+        ArgumentCaptor<AudioManager.OnCommunicationDeviceChangedListener>
+
+    @Mock private lateinit var context: Context
+    @Mock private lateinit var audioManager: AudioManager
+    @Mock private lateinit var communicationDevice: AudioDeviceInfo
+
+    private val volumeByStream: MutableMap<Int, Int> = mutableMapOf()
+    private val isAffectedByRingerModeByStream: MutableMap<Int, Boolean> = mutableMapOf()
+    private val isMuteByStream: MutableMap<Int, Boolean> = mutableMapOf()
+    private val testScope = TestScope()
+
+    private lateinit var underTest: AudioRepository
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        `when`(audioManager.mode).thenReturn(AudioManager.MODE_RINGTONE)
+        `when`(audioManager.communicationDevice).thenReturn(communicationDevice)
+        `when`(audioManager.getStreamMinVolume(anyInt())).thenReturn(MIN_VOLUME)
+        `when`(audioManager.getStreamMaxVolume(anyInt())).thenReturn(MAX_VOLUME)
+        `when`(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL)
+        `when`(audioManager.setStreamVolume(anyInt(), anyInt(), anyInt())).then {
+            volumeByStream[it.arguments[0] as Int] = it.arguments[1] as Int
+            triggerIntent(AudioManager.ACTION_VOLUME_CHANGED)
+        }
+        `when`(audioManager.adjustStreamVolume(anyInt(), anyInt(), anyInt())).then {
+            isMuteByStream[it.arguments[0] as Int] = it.arguments[2] == AudioManager.ADJUST_MUTE
+            triggerIntent(AudioManager.STREAM_MUTE_CHANGED_ACTION)
+        }
+        `when`(audioManager.getStreamVolume(anyInt())).thenAnswer {
+            volumeByStream.getOrDefault(it.arguments[0] as Int, 0)
+        }
+        `when`(audioManager.isStreamAffectedByRingerMode(anyInt())).thenAnswer {
+            isAffectedByRingerModeByStream.getOrDefault(it.arguments[0] as Int, false)
+        }
+        `when`(audioManager.isStreamMute(anyInt())).thenAnswer {
+            isMuteByStream.getOrDefault(it.arguments[0] as Int, false)
+        }
+
+        underTest =
+            AudioRepositoryImpl(
+                context,
+                audioManager,
+                testScope.testScheduler,
+                testScope.backgroundScope,
+            )
+    }
+
+    @Test
+    fun audioModeChanges_repositoryEmits() {
+        testScope.runTest {
+            val modes = mutableListOf<Int>()
+            underTest.mode.onEach { modes.add(it) }.launchIn(backgroundScope)
+            runCurrent()
+
+            triggerModeChange(AudioManager.MODE_IN_CALL)
+            runCurrent()
+
+            assertThat(modes).containsExactly(AudioManager.MODE_RINGTONE, AudioManager.MODE_IN_CALL)
+        }
+    }
+
+    @Test
+    fun ringerModeChanges_repositoryEmits() {
+        testScope.runTest {
+            val modes = mutableListOf<RingerMode>()
+            underTest.ringerMode.onEach { modes.add(it) }.launchIn(backgroundScope)
+            runCurrent()
+
+            `when`(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_SILENT)
+            triggerIntent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)
+            runCurrent()
+
+            assertThat(modes)
+                .containsExactly(
+                    RingerMode(AudioManager.RINGER_MODE_NORMAL),
+                    RingerMode(AudioManager.RINGER_MODE_SILENT),
+                )
+        }
+    }
+
+    @Test
+    fun communicationDeviceChanges_repositoryEmits() {
+        testScope.runTest {
+            var device: AudioDeviceInfo? = null
+            underTest.communicationDevice.onEach { device = it }.launchIn(backgroundScope)
+            runCurrent()
+
+            triggerConnectedDeviceChange(communicationDevice)
+            runCurrent()
+
+            assertThat(device).isSameInstanceAs(communicationDevice)
+        }
+    }
+
+    @Test
+    fun adjustingVolume_changesTheStream() {
+        testScope.runTest {
+            val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
+            var streamModel: AudioStreamModel? = null
+            underTest
+                .getAudioStream(audioStream)
+                .onEach { streamModel = it }
+                .launchIn(backgroundScope)
+            runCurrent()
+
+            underTest.setVolume(audioStream, 50)
+            runCurrent()
+
+            assertThat(streamModel)
+                .isEqualTo(
+                    AudioStreamModel(
+                        audioStream = audioStream,
+                        volume = 50,
+                        minVolume = MIN_VOLUME,
+                        maxVolume = MAX_VOLUME,
+                        isAffectedByRingerMode = false,
+                        isMuted = false,
+                    )
+                )
+        }
+    }
+
+    @Test
+    fun adjustingVolume_currentModeIsUpToDate() {
+        testScope.runTest {
+            val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
+            var streamModel: AudioStreamModel? = null
+            underTest
+                .getAudioStream(audioStream)
+                .onEach { streamModel = it }
+                .launchIn(backgroundScope)
+            runCurrent()
+
+            underTest.setVolume(audioStream, 50)
+            runCurrent()
+
+            assertThat(underTest.getCurrentAudioStream(audioStream)).isEqualTo(streamModel)
+        }
+    }
+
+    @Test
+    fun muteStream_mutesTheStream() {
+        testScope.runTest {
+            val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
+            var streamModel: AudioStreamModel? = null
+            underTest
+                .getAudioStream(audioStream)
+                .onEach { streamModel = it }
+                .launchIn(backgroundScope)
+            runCurrent()
+
+            underTest.setMuted(audioStream, true)
+            runCurrent()
+
+            assertThat(streamModel)
+                .isEqualTo(
+                    AudioStreamModel(
+                        audioStream = audioStream,
+                        volume = 0,
+                        minVolume = MIN_VOLUME,
+                        maxVolume = MAX_VOLUME,
+                        isAffectedByRingerMode = false,
+                        isMuted = true,
+                    )
+                )
+        }
+    }
+
+    @Test
+    fun unmuteStream_unmutesTheStream() {
+        testScope.runTest {
+            val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
+            isMuteByStream[audioStream.value] = true
+            var streamModel: AudioStreamModel? = null
+            underTest
+                .getAudioStream(audioStream)
+                .onEach { streamModel = it }
+                .launchIn(backgroundScope)
+            runCurrent()
+
+            underTest.setMuted(audioStream, false)
+            runCurrent()
+
+            assertThat(streamModel)
+                .isEqualTo(
+                    AudioStreamModel(
+                        audioStream = audioStream,
+                        volume = 0,
+                        minVolume = MIN_VOLUME,
+                        maxVolume = MAX_VOLUME,
+                        isAffectedByRingerMode = false,
+                        isMuted = false,
+                    )
+                )
+        }
+    }
+
+    private fun triggerConnectedDeviceChange(communicationDevice: AudioDeviceInfo?) {
+        verify(audioManager)
+            .addOnCommunicationDeviceChangedListener(
+                any(),
+                communicationDeviceListenerCaptor.capture(),
+            )
+        communicationDeviceListenerCaptor.value.onCommunicationDeviceChanged(communicationDevice)
+    }
+
+    private fun triggerModeChange(mode: Int) {
+        verify(audioManager).addOnModeChangedListener(any(), modeListenerCaptor.capture())
+        modeListenerCaptor.value.onModeChanged(mode)
+    }
+
+    private fun triggerIntent(action: String) {
+        verify(context).registerReceiver(receiverCaptor.capture(), any())
+        receiverCaptor.value.onReceive(context, Intent(action))
+    }
+
+    private companion object {
+        const val MIN_VOLUME = 0
+        const val MAX_VOLUME = 100
+    }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt
index 686362f..dddf8e82 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt
@@ -16,9 +16,15 @@
 
 package com.android.settingslib.volume.data.repository
 
+import android.media.AudioDeviceInfo
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.settingslib.volume.shared.model.AudioStreamModel
+import com.android.settingslib.volume.shared.model.RingerMode
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
 
 class FakeAudioRepository : AudioRepository {
 
@@ -26,7 +32,59 @@
     override val mode: StateFlow<Int>
         get() = mutableMode.asStateFlow()
 
+    private val mutableRingerMode = MutableStateFlow(RingerMode(0))
+    override val ringerMode: StateFlow<RingerMode>
+        get() = mutableRingerMode.asStateFlow()
+
+    private val mutableCommunicationDevice = MutableStateFlow<AudioDeviceInfo?>(null)
+    override val communicationDevice: StateFlow<AudioDeviceInfo?>
+        get() = mutableCommunicationDevice.asStateFlow()
+
+    private val models: MutableMap<AudioStream, MutableStateFlow<AudioStreamModel>> = mutableMapOf()
+
+    private fun getAudioStreamModelState(
+        audioStream: AudioStream
+    ): MutableStateFlow<AudioStreamModel> =
+        models.getOrPut(audioStream) {
+            MutableStateFlow(
+                AudioStreamModel(
+                    audioStream = audioStream,
+                    volume = 0,
+                    minVolume = 0,
+                    maxVolume = 0,
+                    isAffectedByRingerMode = false,
+                    isMuted = false,
+                )
+            )
+        }
+
+    override suspend fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> =
+        getAudioStreamModelState(audioStream).asStateFlow()
+
+    override suspend fun getCurrentAudioStream(audioStream: AudioStream): AudioStreamModel =
+        getAudioStreamModelState(audioStream).value
+
+    override suspend fun setVolume(audioStream: AudioStream, volume: Int) {
+        getAudioStreamModelState(audioStream).update { it.copy(volume = volume) }
+    }
+
+    override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) {
+        getAudioStreamModelState(audioStream).update { it.copy(isMuted = isMuted) }
+    }
+
     fun setMode(newMode: Int) {
         mutableMode.value = newMode
     }
+
+    fun setRingerMode(newRingerMode: RingerMode) {
+        mutableRingerMode.value = newRingerMode
+    }
+
+    fun setCommunicationDevice(device: AudioDeviceInfo?) {
+        mutableCommunicationDevice.value = device
+    }
+
+    fun setAudioStreamModel(model: AudioStreamModel) {
+        getAudioStreamModelState(model.audioStream).update { model }
+    }
 }
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt
index 3bc1edc..4dbf865 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt
@@ -17,8 +17,9 @@
 package com.android.settingslib.volume.domain.interactor
 
 import android.media.AudioManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import androidx.test.runner.AndroidJUnit4
+import com.android.settingslib.BaseTest
 import com.android.settingslib.volume.data.repository.FakeAudioRepository
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -33,7 +34,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 @SmallTest
-class AudioModeInteractorTest {
+class AudioModeInteractorTest : BaseTest() {
 
     private val testScope = TestScope()
     private val fakeAudioRepository = FakeAudioRepository()
diff --git a/packages/SettingsLib/tests/unit/Android.bp b/packages/SettingsLib/tests/unit/Android.bp
index 6d6e2ff..e2eda4f 100644
--- a/packages/SettingsLib/tests/unit/Android.bp
+++ b/packages/SettingsLib/tests/unit/Android.bp
@@ -32,5 +32,7 @@
         "androidx.test.ext.junit",
         "androidx.test.runner",
         "truth",
+        "kotlinx_coroutines_test",
+        "mockito-target-minus-junit4",
     ],
 }
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 95e0e1b..e99fcc9 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -908,6 +908,12 @@
     <!-- Permissions required for CTS test - CtsPermissionUiTestCases -->
     <uses-permission android:name="android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES" />
 
+    <!-- Permissions required for CTS test - CtsContactKeysProviderPrivilegedApp -->
+    <uses-permission android:name="android.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS"/>
+
+    <!-- Permission required for Cts test ScreenRecordingCallbackTests -->
+    <uses-permission android:name="android.permission.DETECT_SCREEN_RECORDING" />
+
     <application
         android:label="@string/app_label"
         android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 3db99f28..ba3026e 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -67,6 +67,16 @@
 }
 
 flag {
+    name: "nssl_falsing_fix"
+    namespace: "systemui"
+    description: "Minor touch changes to prevent falsing errors in NSSL"
+    bug: "316551193"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "refactor_get_current_user"
     namespace: "systemui"
     description: "KeyguardUpdateMonitor.getCurrentUser() was providing outdated results."
@@ -94,7 +104,7 @@
         " standard background color is desired.  This was the behavior before we discovered"
         " a resources threading issue, which we worked around by tinting the notification"
         " backgrounds and footer buttons."
-    bug: "294347738"
+    bug: "294830092"
 }
 
 flag {
@@ -323,13 +333,6 @@
 }
 
 flag {
-    name: "screenshare_notification_hiding"
-    namespace: "systemui"
-    description: "Enable hiding of notifications during screenshare"
-    bug: "312784809"
-}
-
-flag {
    name: "run_fingerprint_detect_on_dismissible_keyguard"
    namespace: "systemui"
    description: "Run fingerprint detect instead of authenticate if the keyguard is dismissible."
@@ -365,9 +368,9 @@
 }
 
 flag {
-   name: "enable_keyguard_compose"
+   name: "compose_lockscreen"
    namespace: "systemui"
-   description: "Enables the compose version of keyguard."
+   description: "Enables the compose version of lockscreen that runs standalone, outside of Flexiglass."
    bug: "301968149"
 }
 
@@ -384,3 +387,11 @@
    description: "Enables on-screen contextual tip about how to take screenshot."
    bug: "322891421"
 }
+
+flag {
+   name: "shaderlib_loading_effect_refactor"
+   namespace: "systemui"
+   description: "Extend shader library to provide the common loading effects."
+   bug: "282007590"
+}
+
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt
index 68e57b5..071433e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt
@@ -53,7 +53,7 @@
             stickyKeys.forEach { (key, isLocked) ->
                 key(key) {
                     Text(
-                        text = key.text,
+                        text = key.displayedText,
                         fontWeight = if (isLocked.locked) FontWeight.Bold else FontWeight.Normal
                     )
                 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 5258078..d904c8b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -504,6 +504,7 @@
 
         layoutImpl.density = density
         layoutImpl.swipeSourceDetector = swipeSourceDetector
+        layoutImpl.transitionInterceptionThreshold = transitionInterceptionThreshold
     }
 
     layoutImpl.Content(modifier)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
index e8cc0ec..2dc94a4 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
@@ -38,6 +38,8 @@
 import com.android.compose.test.runMonotonicClockTest
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -47,7 +49,7 @@
 @RunWith(AndroidJUnit4::class)
 class SceneGestureHandlerTest {
     private class TestGestureScope(
-        val coroutineScope: MonotonicClockTestScope,
+        private val testScope: MonotonicClockTestScope,
     ) {
         private val layoutState =
             MutableSceneTransitionLayoutStateImpl(SceneA, EmptyTestTransitions)
@@ -92,17 +94,16 @@
                     swipeSourceDetector = DefaultEdgeDetector,
                     transitionInterceptionThreshold = transitionInterceptionThreshold,
                     builder = scenesBuilder,
-                    coroutineScope = coroutineScope,
+                    coroutineScope = testScope,
                 )
                 .apply { setScenesTargetSizeForTest(LAYOUT_SIZE) }
 
         val sceneGestureHandler = layoutImpl.gestureHandler(Orientation.Vertical)
         val horizontalSceneGestureHandler = layoutImpl.gestureHandler(Orientation.Horizontal)
-        val draggable = sceneGestureHandler.draggable
 
         fun nestedScrollConnection(nestedScrollBehavior: NestedScrollBehavior) =
             SceneNestedScrollHandler(
-                    layoutImpl,
+                    layoutImpl = layoutImpl,
                     orientation = sceneGestureHandler.orientation,
                     topOrLeftBehavior = nestedScrollBehavior,
                     bottomOrRightBehavior = nestedScrollBehavior,
@@ -117,8 +118,12 @@
         fun up(fractionOfScreen: Float) =
             if (fractionOfScreen < 0f) error("use down()") else -down(fractionOfScreen)
 
-        // Offset y: 10% of the screen
-        val offsetY10 = Offset(x = 0f, y = down(0.1f))
+        fun downOffset(fractionOfScreen: Float) =
+            if (fractionOfScreen < 0f) {
+                error("upOffset() is required, not implemented yet")
+            } else {
+                Offset(x = 0f, y = down(fractionOfScreen))
+            }
 
         val transitionState: TransitionState
             get() = layoutState.transitionState
@@ -127,15 +132,15 @@
             get() = (transitionState as Transition).progress
 
         fun advanceUntilIdle() {
-            coroutineScope.testScheduler.advanceUntilIdle()
+            testScope.testScheduler.advanceUntilIdle()
         }
 
         fun runCurrent() {
-            coroutineScope.testScheduler.runCurrent()
+            testScope.testScheduler.runCurrent()
         }
 
         fun assertIdle(currentScene: SceneKey) {
-            assertWithMessage("transitionState must be Idle").that(transitionState is Idle).isTrue()
+            assertThat(transitionState).isInstanceOf(Idle::class.java)
             assertWithMessage("currentScene does not match")
                 .that(transitionState.currentScene)
                 .isEqualTo(currentScene)
@@ -148,11 +153,8 @@
             progress: Float? = null,
             isUserInputOngoing: Boolean? = null
         ) {
-            val transition = transitionState
-            assertWithMessage("transitionState must be Transition")
-                .that(transition is Transition)
-                .isTrue()
-            transition as Transition
+            assertThat(transitionState).isInstanceOf(Transition::class.java)
+            val transition = transitionState as Transition
 
             if (currentScene != null)
                 assertWithMessage("currentScene does not match")
@@ -180,47 +182,115 @@
                     .that(transition.isUserInputOngoing)
                     .isEqualTo(isUserInputOngoing)
         }
+
+        fun onDragStarted(
+            startedPosition: Offset = Offset.Zero,
+            overSlop: Float,
+            pointersDown: Int = 1
+        ) {
+            // overSlop should be 0f only if the drag gesture starts with startDragImmediately
+            if (overSlop == 0f) error("Consider using onDragStartedImmediately()")
+            onDragStarted(sceneGestureHandler.draggable, startedPosition, overSlop, pointersDown)
+        }
+
+        fun onDragStartedImmediately(startedPosition: Offset = Offset.Zero, pointersDown: Int = 1) {
+            onDragStarted(
+                sceneGestureHandler.draggable,
+                startedPosition,
+                overSlop = 0f,
+                pointersDown
+            )
+        }
+
+        fun onDragStarted(
+            draggableHandler: DraggableHandler,
+            startedPosition: Offset = Offset.Zero,
+            overSlop: Float = 0f,
+            pointersDown: Int = 1
+        ) {
+            draggableHandler.onDragStarted(
+                startedPosition = startedPosition,
+                overSlop = overSlop,
+                pointersDown = pointersDown,
+            )
+
+            // MultiPointerDraggable will always call onDelta with the initial overSlop right after
+            onDelta(pixels = overSlop)
+        }
+
+        fun onDelta(pixels: Float) {
+            sceneGestureHandler.draggable.onDelta(pixels = pixels)
+        }
+
+        fun onDragStopped(velocity: Float) {
+            sceneGestureHandler.draggable.onDragStopped(velocity = velocity)
+            runCurrent()
+        }
+
+        fun NestedScrollConnection.scroll(
+            available: Offset,
+            consumedByScroll: Offset = Offset.Zero,
+        ) {
+            val consumedByPreScroll =
+                onPreScroll(
+                    available = available,
+                    source = NestedScrollSource.Drag,
+                )
+            val consumed = consumedByPreScroll + consumedByScroll
+
+            onPostScroll(
+                consumed = consumed,
+                available = available - consumed,
+                source = NestedScrollSource.Drag
+            )
+        }
+
+        fun NestedScrollConnection.preFling(
+            available: Velocity,
+            coroutineScope: CoroutineScope = testScope,
+        ) {
+            // onPreFling is a suspend function that returns the consumed velocity once it finishes
+            // consuming it. In the current scenario, it returns after completing the animation.
+            // To return immediately, we can initiate a job that allows us to check the status
+            // before the animation starts.
+            coroutineScope.launch { onPreFling(available = available) }
+            runCurrent()
+        }
     }
 
     private fun runGestureTest(block: suspend TestGestureScope.() -> Unit) {
-        runMonotonicClockTest { TestGestureScope(coroutineScope = this).block() }
-    }
+        runMonotonicClockTest {
+            val testGestureScope = TestGestureScope(testScope = this)
 
-    private fun DraggableHandler.onDragStarted(
-        overSlop: Float = 0f,
-        startedPosition: Offset = Offset.Zero,
-    ) {
-        onDragStarted(startedPosition, overSlop)
-        // MultiPointerDraggable will always call onDelta with the initial overSlop right after
-        onDelta(overSlop)
+            // run the test
+            testGestureScope.block()
+        }
     }
 
     @Test fun testPreconditions() = runGestureTest { assertIdle(currentScene = SceneA) }
 
     @Test
     fun onDragStarted_shouldStartATransition() = runGestureTest {
-        draggable.onDragStarted(overSlop = down(0.1f))
+        onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
         assertTransition(currentScene = SceneA)
     }
 
     @Test
     fun afterSceneTransitionIsStarted_interceptDragEvents() = runGestureTest {
-        draggable.onDragStarted(overSlop = down(0.1f))
+        onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
         assertTransition(currentScene = SceneA)
         assertThat(progress).isEqualTo(0.1f)
 
-        draggable.onDelta(pixels = down(0.1f))
+        onDelta(pixels = down(fractionOfScreen = 0.1f))
         assertThat(progress).isEqualTo(0.2f)
     }
 
     @Test
     fun onDragStoppedAfterDrag_velocityLowerThanThreshold_remainSameScene() = runGestureTest {
-        draggable.onDragStarted(overSlop = down(0.1f))
+        onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
         assertTransition(currentScene = SceneA)
 
-        draggable.onDragStopped(
-            velocity = velocityThreshold - 0.01f,
-        )
+        onDragStopped(velocity = velocityThreshold - 0.01f)
         assertTransition(currentScene = SceneA)
 
         // wait for the stop animation
@@ -230,11 +300,10 @@
 
     @Test
     fun onDragStoppedAfterDrag_velocityAtLeastThreshold_goToNextScene() = runGestureTest {
-        draggable.onDragStarted(overSlop = down(0.1f))
+        onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
         assertTransition(currentScene = SceneA)
 
-        draggable.onDragStopped(velocity = velocityThreshold)
-
+        onDragStopped(velocity = velocityThreshold)
         assertTransition(currentScene = SceneC)
 
         // wait for the stop animation
@@ -244,10 +313,10 @@
 
     @Test
     fun onDragStoppedAfterStarted_returnToIdle() = runGestureTest {
-        draggable.onDragStarted(overSlop = down(0.1f))
+        onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
         assertTransition(currentScene = SceneA)
 
-        draggable.onDragStopped(velocity = 0f)
+        onDragStopped(velocity = 0f)
         advanceUntilIdle()
         assertIdle(currentScene = SceneA)
     }
@@ -255,7 +324,7 @@
     @Test
     fun onDragReversedDirection_changeToScene() = runGestureTest {
         // Drag A -> B with progress 0.6
-        draggable.onDragStarted(overSlop = -60f)
+        onDragStarted(overSlop = -60f)
         assertTransition(
             currentScene = SceneA,
             fromScene = SceneA,
@@ -264,7 +333,7 @@
         )
 
         // Reverse direction such that A -> C now with 0.4
-        draggable.onDelta(100f)
+        onDelta(pixels = 100f)
         assertTransition(
             currentScene = SceneA,
             fromScene = SceneA,
@@ -273,7 +342,7 @@
         )
 
         // After the drag stopped scene C should be committed
-        draggable.onDragStopped(velocity = velocityThreshold)
+        onDragStopped(velocity = velocityThreshold)
         assertTransition(currentScene = SceneC, fromScene = SceneA, toScene = SceneC)
 
         // wait for the stop animation
@@ -283,9 +352,12 @@
 
     @Test
     fun onDragStartedWithoutActionsInBothDirections_stayIdle() = runGestureTest {
-        horizontalSceneGestureHandler.draggable.onDragStarted(up(0.3f))
+        val horizontalDraggableHandler = horizontalSceneGestureHandler.draggable
+
+        onDragStarted(horizontalDraggableHandler, overSlop = up(fractionOfScreen = 0.3f))
         assertIdle(currentScene = SceneA)
-        horizontalSceneGestureHandler.draggable.onDragStarted(down(0.3f))
+
+        onDragStarted(horizontalDraggableHandler, overSlop = down(fractionOfScreen = 0.3f))
         assertIdle(currentScene = SceneA)
     }
 
@@ -294,7 +366,7 @@
         navigateToSceneC()
 
         // We are on SceneC which has no action in Down direction
-        draggable.onDragStarted(10f)
+        onDragStarted(overSlop = 10f)
         assertTransition(
             currentScene = SceneC,
             fromScene = SceneC,
@@ -303,7 +375,7 @@
         )
 
         // Reverse drag direction, it will consume the previous drag
-        draggable.onDelta(-10f)
+        onDelta(pixels = -10f)
         assertTransition(
             currentScene = SceneC,
             fromScene = SceneC,
@@ -312,7 +384,7 @@
         )
 
         // Continue reverse drag direction, it should record progress to Scene B
-        draggable.onDelta(-10f)
+        onDelta(pixels = -10f)
         assertTransition(
             currentScene = SceneC,
             fromScene = SceneC,
@@ -326,7 +398,10 @@
         navigateToSceneC()
 
         // Start dragging from the bottom
-        draggable.onDragStarted(up(0.1f), Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE))
+        onDragStarted(
+            startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE),
+            overSlop = up(fractionOfScreen = 0.1f)
+        )
         assertTransition(
             currentScene = SceneC,
             fromScene = SceneC,
@@ -337,14 +412,14 @@
 
     @Test
     fun onDragToExactlyZero_toSceneIsSet() = runGestureTest {
-        draggable.onDragStarted(down(0.3f))
+        onDragStarted(overSlop = down(fractionOfScreen = 0.3f))
         assertTransition(
             currentScene = SceneA,
             fromScene = SceneA,
             toScene = SceneC,
             progress = 0.3f
         )
-        draggable.onDelta(up(0.3f))
+        onDelta(pixels = up(fractionOfScreen = 0.3f))
         assertTransition(
             currentScene = SceneA,
             fromScene = SceneA,
@@ -355,8 +430,8 @@
 
     private fun TestGestureScope.navigateToSceneC() {
         assertIdle(currentScene = SceneA)
-        draggable.onDragStarted(down(1f))
-        draggable.onDragStopped(0f)
+        onDragStarted(overSlop = down(fractionOfScreen = 1f))
+        onDragStopped(velocity = 0f)
         advanceUntilIdle()
         assertIdle(currentScene = SceneC)
     }
@@ -364,7 +439,7 @@
     @Test
     fun onAccelaratedScroll_scrollToThirdScene() = runGestureTest {
         // Drag A -> B with progress 0.2
-        draggable.onDragStarted(overSlop = up(0.2f))
+        onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
         assertTransition(
             currentScene = SceneA,
             fromScene = SceneA,
@@ -373,13 +448,13 @@
         )
 
         // Start animation A -> B with progress 0.2 -> 1.0
-        draggable.onDragStopped(velocity = -velocityThreshold)
+        onDragStopped(velocity = -velocityThreshold)
         assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
 
         // While at A -> B do a 100% screen drag (progress 1.2). This should go past B and change
         // the transition to B -> C with progress 0.2
-        draggable.onDragStarted()
-        draggable.onDelta(up(1f))
+        onDragStartedImmediately()
+        onDelta(pixels = up(fractionOfScreen = 1f))
         assertTransition(
             currentScene = SceneB,
             fromScene = SceneB,
@@ -388,7 +463,7 @@
         )
 
         // After the drag stopped scene C should be committed
-        draggable.onDragStopped(velocity = -velocityThreshold)
+        onDragStopped(velocity = -velocityThreshold)
         assertTransition(currentScene = SceneC, fromScene = SceneB, toScene = SceneC)
 
         // wait for the stop animation
@@ -398,9 +473,9 @@
 
     @Test
     fun onAccelaratedScrollBothTargetsBecomeNull_settlesToIdle() = runGestureTest {
-        draggable.onDragStarted(overSlop = up(0.2f))
-        draggable.onDelta(up(0.2f))
-        draggable.onDragStopped(velocity = -velocityThreshold)
+        onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
+        onDelta(pixels = up(fractionOfScreen = 0.2f))
+        onDragStopped(velocity = -velocityThreshold)
         assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
 
         mutableUserActionsA.remove(Swipe.Up)
@@ -409,83 +484,79 @@
         mutableUserActionsB.remove(Swipe.Down)
 
         // start accelaratedScroll and scroll over to B -> null
-        draggable.onDragStarted()
-        draggable.onDelta(up(0.5f))
-        draggable.onDelta(up(0.5f))
+        onDragStartedImmediately()
+        onDelta(pixels = up(fractionOfScreen = 0.5f))
+        onDelta(pixels = up(fractionOfScreen = 0.5f))
 
         // here onDragStopped is already triggered, but subsequent onDelta/onDragStopped calls may
         // still be called. Make sure that they don't crash or change the scene
-        draggable.onDelta(up(0.5f))
-        draggable.onDragStopped(0f)
+        onDelta(pixels = up(fractionOfScreen = 0.5f))
+        onDragStopped(velocity = 0f)
 
         advanceUntilIdle()
         assertIdle(SceneB)
 
         // These events can still come in after the animation has settled
-        draggable.onDelta(up(0.5f))
-        draggable.onDragStopped(0f)
+        onDelta(pixels = up(fractionOfScreen = 0.5f))
+        onDragStopped(velocity = 0f)
         assertIdle(SceneB)
     }
 
     @Test
     fun onDragTargetsChanged_targetStaysTheSame() = runGestureTest {
-        draggable.onDragStarted(up(0.1f))
+        onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
         assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
 
         mutableUserActionsA[Swipe.Up] = SceneC
-        draggable.onDelta(up(0.1f))
+        onDelta(pixels = up(fractionOfScreen = 0.1f))
         // target stays B even though UserActions changed
         assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.2f)
-        draggable.onDragStopped(down(0.1f))
+        onDragStopped(velocity = down(fractionOfScreen = 0.1f))
         advanceUntilIdle()
 
         // now target changed to C for new drag
-        draggable.onDragStarted(up(0.1f))
+        onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
         assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.1f)
     }
 
     @Test
     fun onDragTargetsChanged_targetsChangeWhenStartingNewDrag() = runGestureTest {
-        draggable.onDragStarted(up(0.1f))
+        onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
         assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
 
         mutableUserActionsA[Swipe.Up] = SceneC
-        draggable.onDelta(up(0.1f))
-        draggable.onDragStopped(down(0.1f))
+        onDelta(pixels = up(fractionOfScreen = 0.1f))
+        onDragStopped(velocity = down(fractionOfScreen = 0.1f))
 
         // now target changed to C for new drag that started before previous drag settled to Idle
-        draggable.onDragStarted(overSlop = 0f)
-        draggable.onDelta(up(0.1f))
+        onDragStartedImmediately()
+        onDelta(pixels = up(fractionOfScreen = 0.1f))
         assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.3f)
     }
 
     @Test
     fun startGestureDuringAnimatingOffset_shouldImmediatelyStopTheAnimation() = runGestureTest {
-        draggable.onDragStarted(overSlop = down(0.1f))
+        onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
         assertTransition(currentScene = SceneA)
 
-        draggable.onDragStopped(
-            velocity = velocityThreshold,
-        )
+        onDragStopped(velocity = velocityThreshold)
 
-        // The stop animation is not started yet
-        assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isFalse()
-
-        runCurrent()
-
-        assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isTrue()
-        assertThat(sceneGestureHandler.isDrivingTransition).isTrue()
         assertTransition(currentScene = SceneC)
+        assertThat(sceneGestureHandler.isDrivingTransition).isTrue()
+        assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isTrue()
 
         // Start a new gesture while the offset is animating
-        draggable.onDragStarted()
+        onDragStartedImmediately()
         assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isFalse()
     }
 
     @Test
     fun onInitialPreScroll_EdgeWithOverscroll_doNotChangeState() = runGestureTest {
         val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
-        nestedScroll.onPreScroll(available = offsetY10, source = NestedScrollSource.Drag)
+        nestedScroll.onPreScroll(
+            available = downOffset(fractionOfScreen = 0.1f),
+            source = NestedScrollSource.Drag
+        )
         assertIdle(currentScene = SceneA)
     }
 
@@ -509,40 +580,29 @@
         val consumed =
             nestedScroll.onPostScroll(
                 consumed = Offset.Zero,
-                available = offsetY10,
+                available = downOffset(fractionOfScreen = 0.1f),
                 source = NestedScrollSource.Drag
             )
 
         assertTransition(currentScene = SceneA)
         assertThat(progress).isEqualTo(0.1f)
-        assertThat(consumed).isEqualTo(offsetY10)
-    }
-
-    private fun NestedScrollConnection.scroll(
-        available: Offset,
-        consumedByScroll: Offset = Offset.Zero,
-    ) {
-        val consumedByPreScroll =
-            onPreScroll(available = available, source = NestedScrollSource.Drag)
-        val consumed = consumedByPreScroll + consumedByScroll
-        onPostScroll(
-            consumed = consumed,
-            available = available - consumed,
-            source = NestedScrollSource.Drag
-        )
+        assertThat(consumed).isEqualTo(downOffset(fractionOfScreen = 0.1f))
     }
 
     @Test
     fun afterSceneTransitionIsStarted_interceptPreScrollEvents() = runGestureTest {
         val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
-        nestedScroll.scroll(available = offsetY10)
+        nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
         assertTransition(currentScene = SceneA)
 
         assertThat(progress).isEqualTo(0.1f)
 
         // start intercept preScroll
         val consumed =
-            nestedScroll.onPreScroll(available = offsetY10, source = NestedScrollSource.Drag)
+            nestedScroll.onPreScroll(
+                available = downOffset(fractionOfScreen = 0.1f),
+                source = NestedScrollSource.Drag
+            )
         assertThat(progress).isEqualTo(0.2f)
 
         // do nothing on postScroll
@@ -553,12 +613,12 @@
         )
         assertThat(progress).isEqualTo(0.2f)
 
-        nestedScroll.scroll(available = offsetY10)
+        nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
         assertThat(progress).isEqualTo(0.3f)
         assertTransition(currentScene = SceneA)
     }
 
-    private suspend fun TestGestureScope.preScrollAfterSceneTransition(
+    private fun TestGestureScope.preScrollAfterSceneTransition(
         firstScroll: Float,
         secondScroll: Float
     ) {
@@ -567,10 +627,13 @@
         nestedScroll.scroll(available = Offset(0f, firstScroll))
 
         // stop scene transition (start the "stop animation")
-        nestedScroll.onPreFling(available = Velocity.Zero)
+        nestedScroll.preFling(available = Velocity.Zero)
 
         // a pre scroll event, that could be intercepted by SceneGestureHandler
-        nestedScroll.onPreScroll(Offset(0f, secondScroll), NestedScrollSource.Drag)
+        nestedScroll.onPreScroll(
+            available = Offset(0f, secondScroll),
+            source = NestedScrollSource.Drag
+        )
     }
 
     @Test
@@ -610,16 +673,17 @@
 
         preScrollAfterSceneTransition(firstScroll = firstScroll, secondScroll = secondScroll)
 
+        advanceUntilIdle()
         assertIdle(SceneB)
     }
 
     @Test
     fun onPreFling_velocityLowerThanThreshold_remainSameScene() = runGestureTest {
         val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
-        nestedScroll.scroll(available = offsetY10)
+        nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
         assertTransition(currentScene = SceneA)
 
-        nestedScroll.onPreFling(available = Velocity.Zero)
+        nestedScroll.preFling(available = Velocity.Zero)
         assertTransition(currentScene = SceneA)
 
         // wait for the stop animation
@@ -627,15 +691,15 @@
         assertIdle(currentScene = SceneA)
     }
 
-    private suspend fun TestGestureScope.flingAfterScroll(
+    private fun TestGestureScope.flingAfterScroll(
         use: NestedScrollBehavior,
         idleAfterScroll: Boolean,
     ) {
         val nestedScroll = nestedScrollConnection(nestedScrollBehavior = use)
-        nestedScroll.scroll(available = offsetY10)
+        nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
         if (idleAfterScroll) assertIdle(SceneA) else assertTransition(SceneA)
 
-        nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold))
+        nestedScroll.preFling(available = Velocity(0f, velocityThreshold))
     }
 
     @Test
@@ -679,19 +743,22 @@
     }
 
     /** we started the scroll in the scene, then fling with the velocityThreshold */
-    private suspend fun TestGestureScope.flingAfterScrollStartedInScene(
+    private fun TestGestureScope.flingAfterScrollStartedInScene(
         use: NestedScrollBehavior,
         idleAfterScroll: Boolean,
     ) {
         val nestedScroll = nestedScrollConnection(nestedScrollBehavior = use)
         // scroll consumed in child
-        nestedScroll.scroll(available = offsetY10, consumedByScroll = offsetY10)
+        nestedScroll.scroll(
+            available = downOffset(fractionOfScreen = 0.1f),
+            consumedByScroll = downOffset(fractionOfScreen = 0.1f)
+        )
 
         // scroll offsetY10 is all available for parents
-        nestedScroll.scroll(available = offsetY10)
+        nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
         if (idleAfterScroll) assertIdle(SceneA) else assertTransition(SceneA)
 
-        nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold))
+        nestedScroll.preFling(available = Velocity(0f, velocityThreshold))
     }
 
     @Test
@@ -732,20 +799,20 @@
 
     @Test
     fun beforeDraggableStart_drag_shouldBeIgnored() = runGestureTest {
-        draggable.onDelta(down(0.1f))
+        onDelta(pixels = down(fractionOfScreen = 0.1f))
         assertIdle(currentScene = SceneA)
     }
 
     @Test
     fun beforeDraggableStart_stop_shouldBeIgnored() = runGestureTest {
-        draggable.onDragStopped(velocityThreshold)
+        onDragStopped(velocity = velocityThreshold)
         assertIdle(currentScene = SceneA)
     }
 
     @Test
     fun beforeNestedScrollStart_stop_shouldBeIgnored() = runGestureTest {
         val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
-        nestedScroll.onPreFling(Velocity(0f, velocityThreshold))
+        nestedScroll.preFling(available = Velocity(0f, velocityThreshold))
         assertIdle(currentScene = SceneA)
     }
 
@@ -753,8 +820,10 @@
     fun startNestedScrollWhileDragging() = runGestureTest {
         val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways)
 
+        val offsetY10 = downOffset(fractionOfScreen = 0.1f)
+
         // Start a drag and then stop it, given that
-        draggable.onDragStarted(overSlop = up(0.1f))
+        onDragStarted(overSlop = up(0.1f))
 
         assertTransition(currentScene = SceneA)
         assertThat(progress).isEqualTo(0.1f)
@@ -764,7 +833,7 @@
         assertThat(progress).isEqualTo(0.2f)
 
         // this should be ignored, we are scrolling now!
-        draggable.onDragStopped(-velocityThreshold)
+        onDragStopped(-velocityThreshold)
         assertTransition(currentScene = SceneA)
 
         nestedScroll.scroll(available = -offsetY10)
@@ -773,7 +842,7 @@
         nestedScroll.scroll(available = -offsetY10)
         assertThat(progress).isEqualTo(0.4f)
 
-        nestedScroll.onPreFling(available = Velocity(0f, -velocityThreshold))
+        nestedScroll.preFling(available = Velocity(0f, -velocityThreshold))
         assertTransition(currentScene = SceneB)
 
         // wait for the stop animation
@@ -788,7 +857,7 @@
 
         // Swipe up from the middle to transition to scene B.
         val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
-        draggable.onDragStarted(startedPosition = middle, overSlop = up(0.1f))
+        onDragStarted(startedPosition = middle, overSlop = up(0.1f))
         assertTransition(
             currentScene = SceneC,
             fromScene = SceneC,
@@ -802,7 +871,7 @@
         // should intercept it. Because it is intercepted, the overSlop passed to onDragStarted()
         // should be 0f.
         assertThat(sceneGestureHandler.shouldImmediatelyIntercept(middle)).isTrue()
-        draggable.onDragStarted(startedPosition = middle, overSlop = 0f)
+        onDragStartedImmediately(startedPosition = middle)
 
         // We should have intercepted the transition, so the transition should be the same object.
         assertTransition(currentScene = SceneC, fromScene = SceneC, toScene = SceneB)
@@ -813,7 +882,7 @@
         // instead animate from C to A.
         val bottom = Offset(SCREEN_SIZE / 2, SCREEN_SIZE)
         assertThat(sceneGestureHandler.shouldImmediatelyIntercept(bottom)).isFalse()
-        draggable.onDragStarted(startedPosition = bottom, overSlop = up(0.1f))
+        onDragStarted(startedPosition = bottom, overSlop = up(0.1f))
 
         assertTransition(
             currentScene = SceneC,
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
index fbcd5b2..0245cf2 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
@@ -3,7 +3,9 @@
 import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.TestMonotonicFrameClock
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import kotlinx.coroutines.withContext
 
@@ -12,16 +14,38 @@
  * function.
  *
  * The [TestCoroutineScheduler] is passed to provide the functionality to wait for idle.
+ *
+ * Note: Please refer to the documentation for [runTest], as this feature utilizes it. This will
+ * provide a comprehensive understanding of all its behaviors.
  */
-@OptIn(ExperimentalTestApi::class)
+@OptIn(ExperimentalTestApi::class, ExperimentalCoroutinesApi::class)
 fun runMonotonicClockTest(block: suspend MonotonicClockTestScope.() -> Unit) = runTest {
-    // We need a CoroutineScope (like a TestScope) to create a TestMonotonicFrameClock.
-    withContext(TestMonotonicFrameClock(this)) {
-        MonotonicClockTestScope(coroutineScope = this, testScheduler = testScheduler).block()
+    val testScope: TestScope = this
+
+    withContext(TestMonotonicFrameClock(coroutineScope = testScope)) {
+        val testScopeWithMonotonicFrameClock: CoroutineScope = this
+
+        val scope =
+            MonotonicClockTestScope(
+                testScope = testScopeWithMonotonicFrameClock,
+                testScheduler = testScope.testScheduler,
+                backgroundScope = backgroundScope,
+            )
+
+        // Run the test
+        scope.block()
     }
 }
 
+/**
+ * A coroutine scope that for launching test coroutines for Compose.
+ *
+ * @param testScheduler The delay-skipping scheduler used by the test dispatchers running the code
+ *   in this scope (see [TestScope.testScheduler]).
+ * @param backgroundScope A scope for background work (see [TestScope.backgroundScope]).
+ */
 class MonotonicClockTestScope(
-    coroutineScope: CoroutineScope,
-    val testScheduler: TestCoroutineScheduler
-) : CoroutineScope by coroutineScope
+    testScope: CoroutineScope,
+    val testScheduler: TestCoroutineScheduler,
+    val backgroundScope: CoroutineScope,
+) : CoroutineScope by testScope
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
index 4b21105..e39d7ed 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
@@ -20,8 +20,10 @@
 import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.withContext
@@ -32,6 +34,12 @@
     private val backgroundDispatcher: CoroutineDispatcher,
     private val secureSettingsRepository: SecureSettingsRepository,
 ) {
+    val isNotificationHistoryEnabled: Flow<Boolean> =
+        secureSettingsRepository
+            .intSetting(name = Settings.Secure.NOTIFICATION_HISTORY_ENABLED)
+            .map { it == 1 }
+            .distinctUntilChanged()
+
     /** The current state of the notification setting. */
     val isShowNotificationsOnLockScreenEnabled: StateFlow<Boolean> =
         secureSettingsRepository
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt
index 9ec6ec8..04e8090 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt
@@ -23,6 +23,8 @@
 class NotificationSettingsInteractor(
     private val repository: NotificationSettingsRepository,
 ) {
+    val isNotificationHistoryEnabled = repository.isNotificationHistoryEnabled
+
     /** Should notifications be visible on the lockscreen? */
     val isShowNotificationsOnLockScreenEnabled: StateFlow<Boolean> =
         repository.isShowNotificationsOnLockScreenEnabled
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
index 1642e52..45f98be 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
@@ -72,7 +72,7 @@
         testScope.runTest {
             val mediaModel = collectLastValue(underTest.mediaModel)
             runCurrent()
-            assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
+            assertThat(mediaModel()?.hasActiveMediaOrRecommendation).isFalse()
         }
 
     @Test
@@ -84,16 +84,16 @@
             // Initial value is false.
             val mediaModel = collectLastValue(underTest.mediaModel)
             runCurrent()
-            assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
+            assertThat(mediaModel()?.hasActiveMediaOrRecommendation).isFalse()
 
             // Change to media available and notify the listener.
-            whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
+            whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true)
             whenever(mediaData.createdTimestampMillis).thenReturn(1234L)
             mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)
             runCurrent()
 
             // Media active now returns true.
-            assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isTrue()
+            assertThat(mediaModel()?.hasActiveMediaOrRecommendation).isTrue()
             assertThat(mediaModel()?.createdTimestampMillis).isEqualTo(1234L)
         }
 
@@ -104,20 +104,20 @@
             verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
 
             // Change to media available and notify the listener.
-            whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
+            whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true)
             mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)
             runCurrent()
 
             // Media active now returns true.
             val mediaModel = collectLastValue(underTest.mediaModel)
-            assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isTrue()
+            assertThat(mediaModel()?.hasActiveMediaOrRecommendation).isTrue()
 
             // Change to media unavailable and notify the listener.
-            whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(false)
+            whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(false)
             mediaDataListenerCaptor.value.onMediaDataRemoved("key")
             runCurrent()
 
             // Media active now returns false.
-            assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
+            assertThat(mediaModel()?.hasActiveMediaOrRecommendation).isFalse()
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt
index efccf7a..e4ce6cb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt
@@ -17,7 +17,7 @@
 
 import com.android.systemui.controls.dagger.ControlsComponent
 import com.android.systemui.controls.management.ControlsListingController
-import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.panels.authorizedPanelsRepository
 import com.android.systemui.controls.panels.selectedComponentRepository
 import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
 import com.android.systemui.kosmos.Kosmos
@@ -38,4 +38,3 @@
 
 val Kosmos.controlsComponent by Kosmos.Fixture<ControlsComponent> { mock() }
 val Kosmos.controlsListingController by Kosmos.Fixture<ControlsListingController> { mock() }
-val Kosmos.authorizedPanelsRepository by Kosmos.Fixture<AuthorizedPanelsRepository> { mock() }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
index ce74a90..6ad32cc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
@@ -27,13 +27,14 @@
 import com.android.systemui.controls.dagger.ControlsComponent
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.panels.AuthorizedPanelsRepository
-import com.android.systemui.controls.panels.FakeSelectedComponentRepository
 import com.android.systemui.controls.panels.SelectedComponentRepository
+import com.android.systemui.controls.panels.authorizedPanelsRepository
 import com.android.systemui.controls.panels.selectedComponentRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.settings.fakeUserTracker
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.user.data.repository.fakeUserRepository
@@ -41,6 +42,9 @@
 import com.android.systemui.util.mockito.withArgCaptor
 import com.google.common.truth.Truth.assertThat
 import java.util.Optional
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -48,6 +52,7 @@
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class HomeControlsComponentInteractorTest : SysuiTestCase() {
@@ -59,20 +64,20 @@
     private lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
     private lateinit var underTest: HomeControlsComponentInteractor
     private lateinit var userRepository: FakeUserRepository
-    private lateinit var selectedComponentRepository: FakeSelectedComponentRepository
+    private lateinit var selectedComponentRepository: SelectedComponentRepository
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        userRepository = kosmos.fakeUserRepository
-        userRepository.setUserInfos(listOf(PRIMARY_USER, ANOTHER_USER))
 
         controlsComponent = kosmos.controlsComponent
         authorizedPanelsRepository = kosmos.authorizedPanelsRepository
         controlsListingController = kosmos.controlsListingController
         selectedComponentRepository = kosmos.selectedComponentRepository
 
-        selectedComponentRepository.setCurrentUserHandle(PRIMARY_USER.userHandle)
+        userRepository = kosmos.fakeUserRepository
+        userRepository.setUserInfos(listOf(PRIMARY_USER, ANOTHER_USER))
+
         whenever(controlsComponent.getControlsListingController())
             .thenReturn(Optional.of(controlsListingController))
 
@@ -90,14 +95,13 @@
     fun testPanelComponentReturnsComponentNameForSelectedItemByUser() =
         with(kosmos) {
             testScope.runTest {
-                whenever(authorizedPanelsRepository.getAuthorizedPanels())
-                    .thenReturn(setOf(TEST_PACKAGE_PANEL))
-                userRepository.setSelectedUserInfo(PRIMARY_USER)
+                setActiveUser(PRIMARY_USER)
+                authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE))
                 selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
                 val actualValue by collectLastValue(underTest.panelComponent)
                 assertThat(actualValue).isNull()
                 runServicesUpdate()
-                assertThat(actualValue).isEqualTo(TEST_COMPONENT_PANEL)
+                assertThat(actualValue).isEqualTo(TEST_COMPONENT)
             }
         }
 
@@ -105,16 +109,15 @@
     fun testPanelComponentReturnsComponentNameAsInitialValueWithoutServiceUpdate() =
         with(kosmos) {
             testScope.runTest {
-                whenever(authorizedPanelsRepository.getAuthorizedPanels())
-                    .thenReturn(setOf(TEST_PACKAGE_PANEL))
-                userRepository.setSelectedUserInfo(PRIMARY_USER)
+                setActiveUser(PRIMARY_USER)
+                authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE))
                 selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
                 whenever(controlsListingController.getCurrentServices())
                     .thenReturn(
-                        listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
+                        listOf(ControlsServiceInfo(TEST_COMPONENT, "panel", hasPanel = true))
                     )
                 val actualValue by collectLastValue(underTest.panelComponent)
-                assertThat(actualValue).isEqualTo(TEST_COMPONENT_PANEL)
+                assertThat(actualValue).isEqualTo(TEST_COMPONENT)
             }
         }
 
@@ -122,9 +125,8 @@
     fun testPanelComponentReturnsNullForHomeControlsThatDoesNotSupportPanel() =
         with(kosmos) {
             testScope.runTest {
-                whenever(authorizedPanelsRepository.getAuthorizedPanels())
-                    .thenReturn(setOf(TEST_PACKAGE_PANEL))
-                userRepository.setSelectedUserInfo(PRIMARY_USER)
+                setActiveUser(PRIMARY_USER)
+                authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE))
                 selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_NON_PANEL)
                 val actualValue by collectLastValue(underTest.panelComponent)
                 assertThat(actualValue).isNull()
@@ -137,8 +139,8 @@
     fun testPanelComponentReturnsNullWhenPanelIsUnauthorized() =
         with(kosmos) {
             testScope.runTest {
-                whenever(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf())
-                userRepository.setSelectedUserInfo(PRIMARY_USER)
+                setActiveUser(PRIMARY_USER)
+                authorizedPanelsRepository.removeAuthorizedPanels(setOf(TEST_PACKAGE))
                 selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
                 val actualValue by collectLastValue(underTest.panelComponent)
                 assertThat(actualValue).isNull()
@@ -151,17 +153,24 @@
     fun testPanelComponentReturnsComponentNameForDifferentUsers() =
         with(kosmos) {
             testScope.runTest {
-                whenever(authorizedPanelsRepository.getAuthorizedPanels())
-                    .thenReturn(setOf(TEST_PACKAGE_PANEL))
-                userRepository.setSelectedUserInfo(ANOTHER_USER)
+                val actualValue by collectLastValue(underTest.panelComponent)
+
+                // Secondary user has non-panel selected.
+                setActiveUser(ANOTHER_USER)
                 selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_NON_PANEL)
-                selectedComponentRepository.setCurrentUserHandle(ANOTHER_USER.userHandle)
+
+                // Primary user has panel selected.
+                setActiveUser(PRIMARY_USER)
+                authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE))
                 selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
 
-                val actualValue by collectLastValue(underTest.panelComponent)
-                assertThat(actualValue).isNull()
                 runServicesUpdate()
-                assertThat(actualValue).isEqualTo(TEST_COMPONENT_PANEL)
+                assertThat(actualValue).isEqualTo(TEST_COMPONENT)
+
+                // Back to secondary user, should be null.
+                setActiveUser(ANOTHER_USER)
+                runServicesUpdate()
+                assertThat(actualValue).isNull()
             }
         }
 
@@ -169,8 +178,7 @@
     fun testPanelComponentReturnsNullWhenControlsComponentReturnsNullForListingController() =
         with(kosmos) {
             testScope.runTest {
-                whenever(authorizedPanelsRepository.getAuthorizedPanels())
-                    .thenReturn(setOf(TEST_PACKAGE_PANEL))
+                authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE))
                 whenever(controlsComponent.getControlsListingController())
                     .thenReturn(Optional.empty())
                 userRepository.setSelectedUserInfo(PRIMARY_USER)
@@ -182,11 +190,17 @@
 
     private fun runServicesUpdate(hasPanelBoolean: Boolean = true) {
         val listings =
-            listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = hasPanelBoolean))
+            listOf(ControlsServiceInfo(TEST_COMPONENT, "panel", hasPanel = hasPanelBoolean))
         val callback = withArgCaptor { verify(controlsListingController).addCallback(capture()) }
         callback.onServicesUpdated(listings)
     }
 
+    private suspend fun TestScope.setActiveUser(user: UserInfo) {
+        userRepository.setSelectedUserInfo(user)
+        kosmos.fakeUserTracker.set(listOf(user), 0)
+        runCurrent()
+    }
+
     private fun ControlsServiceInfo(
         componentName: ComponentName,
         label: CharSequence,
@@ -237,19 +251,9 @@
             )
         private const val TEST_PACKAGE = "pkg"
         private val TEST_COMPONENT = ComponentName(TEST_PACKAGE, "service")
-        private const val TEST_PACKAGE_PANEL = "pkg.panel"
-        private val TEST_COMPONENT_PANEL = ComponentName(TEST_PACKAGE_PANEL, "service")
         private val TEST_SELECTED_COMPONENT_PANEL =
-            SelectedComponentRepository.SelectedComponent(
-                TEST_PACKAGE_PANEL,
-                TEST_COMPONENT_PANEL,
-                true
-            )
+            SelectedComponentRepository.SelectedComponent(TEST_PACKAGE, TEST_COMPONENT, true)
         private val TEST_SELECTED_COMPONENT_NON_PANEL =
-            SelectedComponentRepository.SelectedComponent(
-                TEST_PACKAGE_PANEL,
-                TEST_COMPONENT_PANEL,
-                false
-            )
+            SelectedComponentRepository.SelectedComponent(TEST_PACKAGE, TEST_COMPONENT, false)
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
index 6610e70..87b1bbb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.panels.AuthorizedPanelsRepository
 import com.android.systemui.controls.panels.SelectedComponentRepository
+import com.android.systemui.controls.panels.authorizedPanelsRepository
 import com.android.systemui.controls.panels.selectedComponentRepository
 import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
 import com.android.systemui.kosmos.applicationCoroutineScope
@@ -84,8 +85,7 @@
 
         userRepository.setUserInfos(listOf(PRIMARY_USER))
 
-        whenever(authorizedPanelsRepository.getAuthorizedPanels())
-            .thenReturn(setOf(TEST_PACKAGE_PANEL))
+        authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE_PANEL))
 
         whenever(controlsComponent.getControlsListingController())
             .thenReturn(Optional.of(controlsListingController))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelTest.kt
index 2fe4ef78..f400cb1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelTest.kt
@@ -25,9 +25,11 @@
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.domain.interactor.biometricStatusInteractor
 import com.android.systemui.biometrics.domain.interactor.displayStateInteractor
 import com.android.systemui.biometrics.domain.interactor.sideFpsSensorInteractor
 import com.android.systemui.biometrics.fakeFingerprintInteractiveToAuthProvider
+import com.android.systemui.biometrics.shared.model.AuthenticationReason
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.biometrics.shared.model.SensorStrength
 import com.android.systemui.coroutines.collectLastValue
@@ -146,6 +148,7 @@
             kosmos.fakeKeyguardRepository.setIsDozing(false)
             kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
                 AcquiredFingerprintAuthenticationStatus(
+                    AuthenticationReason.DeviceEntryAuthentication,
                     BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
                 )
             )
@@ -165,6 +168,7 @@
             kosmos.fakeKeyguardRepository.setIsDozing(true)
             kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
                 AcquiredFingerprintAuthenticationStatus(
+                    AuthenticationReason.DeviceEntryAuthentication,
                     BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
                 )
             )
@@ -177,6 +181,7 @@
     private fun createViewModel() =
         SideFpsProgressBarViewModel(
             kosmos.applicationContext,
+            kosmos.biometricStatusInteractor,
             kosmos.deviceEntryFingerprintAuthInteractor,
             kosmos.sideFpsSensorInteractor,
             kosmos.dozeServiceHost,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index d6d2509..189ba7b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -522,14 +522,18 @@
         }
 
     @Test
-    fun factoryResetProtectionActive_isNotVisible() =
+    fun deviceProvisioningAndFactoryResetProtection() =
         testScope.runTest {
             val isVisible by collectLastValue(sceneContainerViewModel.isVisible)
-            assertThat(isVisible).isTrue()
-
-            kosmos.fakeDeviceProvisioningRepository.setFactoryResetProtectionActive(isActive = true)
-
+            kosmos.fakeDeviceProvisioningRepository.setDeviceProvisioned(false)
+            kosmos.fakeDeviceProvisioningRepository.setFactoryResetProtectionActive(true)
             assertThat(isVisible).isFalse()
+
+            kosmos.fakeDeviceProvisioningRepository.setFactoryResetProtectionActive(false)
+            assertThat(isVisible).isFalse()
+
+            kosmos.fakeDeviceProvisioningRepository.setDeviceProvisioned(true)
+            assertThat(isVisible).isTrue()
         }
 
     /**
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 1abbc92..12dbf11 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -49,6 +49,7 @@
 import com.android.systemui.scene.shared.model.SceneModel
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
 import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
@@ -164,6 +165,30 @@
         }
 
     @Test
+    fun hydrateVisibility_basedOnDeviceProvisioningAndFactoryResetProtection() =
+        testScope.runTest {
+            val isVisible by collectLastValue(sceneInteractor.isVisible)
+            prepareState(
+                isDeviceUnlocked = true,
+                initialSceneKey = SceneKey.Lockscreen,
+                isDeviceProvisioned = false,
+                isFrpActive = true,
+            )
+
+            underTest.start()
+            assertThat(isVisible).isFalse()
+
+            kosmos.fakeDeviceProvisioningRepository.setFactoryResetProtectionActive(false)
+            assertThat(isVisible).isFalse()
+
+            kosmos.fakeDeviceProvisioningRepository.setDeviceProvisioned(true)
+            assertThat(isVisible).isTrue()
+
+            kosmos.fakeDeviceProvisioningRepository.setFactoryResetProtectionActive(true)
+            assertThat(isVisible).isFalse()
+        }
+
+    @Test
     fun startsInLockscreenScene() =
         testScope.runTest {
             val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
@@ -745,6 +770,8 @@
         authenticationMethod: AuthenticationMethodModel? = null,
         isLockscreenEnabled: Boolean = true,
         startsAwake: Boolean = true,
+        isDeviceProvisioned: Boolean = true,
+        isFrpActive: Boolean = false,
     ): MutableStateFlow<ObservableTransitionState> {
         if (authenticationMethod?.isSecure == true) {
             assert(isLockscreenEnabled) {
@@ -781,6 +808,10 @@
         } else {
             powerInteractor.setAsleepForTest()
         }
+
+        kosmos.fakeDeviceProvisioningRepository.setDeviceProvisioned(isDeviceProvisioned)
+        kosmos.fakeDeviceProvisioningRepository.setFactoryResetProtectionActive(isFrpActive)
+
         runCurrent()
 
         return transitionStateFlow
diff --git a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
new file mode 100644
index 0000000..a877853
--- /dev/null
+++ b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
@@ -0,0 +1,244 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+xmlns:app="http://schemas.android.com/apk/res-auto"
+xmlns:tools="http://schemas.android.com/tools"
+android:layout_width="match_parent"
+android:layout_height="match_parent">
+
+    <ImageView
+        android:id="@+id/logo"
+        android:layout_width="@dimen/biometric_auth_icon_size"
+        android:layout_height="@dimen/biometric_auth_icon_size"
+        android:layout_gravity="center"
+        android:scaleType="fitXY"
+        android:visibility="gone" />
+
+    <ImageView
+        android:id="@+id/background"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:contentDescription="@string/biometric_dialog_empty_space_description"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <View
+        android:id="@+id/panel"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:background="?android:attr/colorBackgroundFloating"
+        android:clickable="true"
+        android:clipToOutline="true"
+        android:importantForAccessibility="no"
+        android:paddingHorizontal="16dp"
+        android:paddingVertical="16dp"
+        android:visibility="visible"
+        app:layout_constraintBottom_toTopOf="@+id/bottomGuideline"
+        app:layout_constraintEnd_toStartOf="@+id/rightGuideline"
+        app:layout_constraintStart_toStartOf="@+id/leftGuideline"
+        app:layout_constraintTop_toTopOf="@+id/title" />
+
+    <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
+        android:id="@+id/biometric_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintVertical_bias="0.8"
+        tools:srcCompat="@tools:sample/avatars" />
+
+    <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
+        android:id="@+id/biometric_icon_overlay"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_gravity="center"
+        android:contentDescription="@null"
+        android:scaleType="fitXY"
+        app:layout_constraintBottom_toBottomOf="@+id/biometric_icon"
+        app:layout_constraintEnd_toEndOf="@+id/biometric_icon"
+        app:layout_constraintHorizontal_bias="1.0"
+        app:layout_constraintStart_toStartOf="@+id/biometric_icon"
+        app:layout_constraintTop_toTopOf="@+id/biometric_icon"
+        app:layout_constraintVertical_bias="0.0" />
+
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="@integer/biometric_dialog_text_gravity"
+        android:singleLine="true"
+        android:marqueeRepeatLimit="1"
+        android:ellipsize="marquee"
+        style="@style/TextAppearance.AuthCredential.Title"
+        app:layout_constraintBottom_toTopOf="@+id/subtitle"
+        app:layout_constraintEnd_toEndOf="@+id/panel"
+        app:layout_constraintStart_toStartOf="@+id/panel" />
+
+    <TextView
+        android:id="@+id/subtitle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="@integer/biometric_dialog_text_gravity"
+        android:singleLine="true"
+        android:marqueeRepeatLimit="1"
+        android:ellipsize="marquee"
+        style="@style/TextAppearance.AuthCredential.Subtitle"
+        app:layout_constraintBottom_toTopOf="@+id/description"
+        app:layout_constraintEnd_toEndOf="@+id/panel"
+        app:layout_constraintStart_toStartOf="@+id/panel" />
+
+    <Space
+        android:id="@+id/space_above_content"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/biometric_prompt_space_above_content"
+        android:visibility="gone"
+        app:layout_constraintTop_toBottomOf="@+id/subtitle"
+        app:layout_constraintEnd_toEndOf="@+id/panel"
+        app:layout_constraintStart_toStartOf="@+id/panel"/>
+
+    <ScrollView
+        android:id="@+id/customized_view_container"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:fillViewport="true"
+        android:fadeScrollbars="false"
+        android:gravity="center_vertical"
+        android:orientation="vertical"
+        android:paddingHorizontal="@dimen/biometric_prompt_content_container_padding_horizontal"
+        android:scrollbars="vertical"
+        android:visibility="gone"
+        app:layout_constraintTop_toBottomOf="@+id/space_above_content"
+        app:layout_constraintBottom_toTopOf="@+id/biometric_icon"
+        app:layout_constraintEnd_toEndOf="@+id/panel"
+        app:layout_constraintStart_toStartOf="@+id/panel"/>
+
+    <TextView
+        android:id="@+id/description"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="24dp"
+        android:scrollbars="vertical"
+        android:gravity="@integer/biometric_dialog_text_gravity"
+        style="@style/TextAppearance.AuthCredential.Description"
+        app:layout_constraintBottom_toTopOf="@+id/biometric_icon"
+        app:layout_constraintEnd_toEndOf="@+id/panel"
+        app:layout_constraintStart_toStartOf="@+id/panel" />
+
+    <TextView
+        android:id="@+id/indicator"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="16dp"
+        android:gravity="center_horizontal"
+        android:textColor="@color/biometric_dialog_gray"
+        android:textSize="12sp"
+        android:accessibilityLiveRegion="polite"
+        android:marqueeRepeatLimit="marquee_forever"
+        android:scrollHorizontally="true"
+        android:fadingEdge="horizontal"
+        app:layout_constraintEnd_toEndOf="@+id/panel"
+        app:layout_constraintHorizontal_bias="0.5"
+        app:layout_constraintStart_toStartOf="@+id/panel"
+        app:layout_constraintTop_toBottomOf="@+id/biometric_icon" />
+
+    <!-- Negative Button, reserved for app -->
+    <Button
+        android:id="@+id/button_negative"
+        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginBottom="8dp"
+        android:layout_marginLeft="8dp"
+        android:ellipsize="end"
+        android:maxLines="2"
+        android:visibility="invisible"
+        app:layout_constraintBottom_toBottomOf="@+id/panel"
+        app:layout_constraintStart_toStartOf="@+id/panel" />
+
+    <!-- Cancel Button, replaces negative button when biometric is accepted -->
+    <Button
+        android:id="@+id/button_cancel"
+        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginBottom="8dp"
+        android:layout_marginLeft="8dp"
+        android:text="@string/cancel"
+        android:visibility="invisible"
+        app:layout_constraintBottom_toBottomOf="@+id/panel"
+        app:layout_constraintStart_toStartOf="@+id/panel" />
+
+    <!-- "Use Credential" Button, replaces if device credential is allowed -->
+    <Button
+        android:id="@+id/button_use_credential"
+        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginBottom="8dp"
+        android:layout_marginLeft="8dp"
+        android:visibility="invisible"
+        app:layout_constraintBottom_toBottomOf="@+id/panel"
+        app:layout_constraintStart_toStartOf="@+id/panel" />
+
+    <!-- Positive Button -->
+    <Button
+        android:id="@+id/button_confirm"
+        style="@*android:style/Widget.DeviceDefault.Button.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginBottom="8dp"
+        android:layout_marginRight="8dp"
+        android:ellipsize="end"
+        android:maxLines="2"
+        android:text="@string/biometric_dialog_confirm"
+        android:visibility="invisible"
+        app:layout_constraintBottom_toBottomOf="@+id/panel"
+        app:layout_constraintEnd_toEndOf="@+id/panel"
+        tools:visibility="invisible" />
+
+    <!-- Try Again Button -->
+    <Button
+        android:id="@+id/button_try_again"
+        style="@*android:style/Widget.DeviceDefault.Button.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginBottom="8dp"
+        android:layout_marginRight="8dp"
+        android:ellipsize="end"
+        android:maxLines="2"
+        android:text="@string/biometric_dialog_try_again"
+        android:visibility="invisible"
+        app:layout_constraintBottom_toBottomOf="@+id/panel"
+        app:layout_constraintEnd_toEndOf="@+id/panel" />
+
+    <!-- Guidelines for setting panel border -->
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/leftGuideline"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        app:layout_constraintGuide_begin="@dimen/biometric_dialog_border_padding" />
+
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/rightGuideline"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />
+
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/bottomGuideline"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 57e308f..3397906 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -20,6 +20,7 @@
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_BIOMETRIC_PROMPT_TRANSITION;
+import static com.android.systemui.Flags.constraintBp;
 
 import android.animation.Animator;
 import android.annotation.IntDef;
@@ -57,6 +58,7 @@
 import android.window.OnBackInvokedCallback;
 import android.window.OnBackInvokedDispatcher;
 
+import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.core.view.AccessibilityDelegateCompat;
 import androidx.core.view.ViewCompat;
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
@@ -153,7 +155,7 @@
     @Nullable private Spaghetti mBiometricView;
     @Nullable private View mCredentialView;
     private final AuthPanelController mPanelController;
-    private final FrameLayout mFrameLayout;
+    private final ViewGroup mLayout;
     private final ImageView mBackgroundView;
     private final ScrollView mBiometricScrollView;
     private final View mPanelView;
@@ -339,11 +341,16 @@
         mBiometricCallback = new BiometricCallback();
 
         final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
-        mFrameLayout = (FrameLayout) layoutInflater.inflate(
-                R.layout.auth_container_view, this, false /* attachToRoot */);
-        addView(mFrameLayout);
-        mBiometricScrollView = mFrameLayout.findViewById(R.id.biometric_scrollview);
-        mBackgroundView = mFrameLayout.findViewById(R.id.background);
+        if (constraintBp()) {
+            mLayout = (ConstraintLayout) layoutInflater.inflate(
+                    R.layout.biometric_prompt_constraint_layout, this, false /* attachToRoot */);
+        } else {
+            mLayout = (FrameLayout) layoutInflater.inflate(
+                    R.layout.auth_container_view, this, false /* attachToRoot */);
+        }
+        mBiometricScrollView = mLayout.findViewById(R.id.biometric_scrollview);
+        addView(mLayout);
+        mBackgroundView = mLayout.findViewById(R.id.background);
         ViewCompat.setAccessibilityDelegate(mBackgroundView, new AccessibilityDelegateCompat() {
             @Override
             public void onInitializeAccessibilityNodeInfo(View host,
@@ -358,7 +365,7 @@
             }
         });
 
-        mPanelView = mFrameLayout.findViewById(R.id.panel);
+        mPanelView = mLayout.findViewById(R.id.panel);
         mPanelController = new AuthPanelController(mContext, mPanelView);
         mBackgroundExecutor = bgExecutor;
         mInteractionJankMonitor = jankMonitor;
@@ -402,20 +409,31 @@
                     new BiometricModalities(fpProps, faceProps),
                     config.mOpPackageName);
 
-            final BiometricPromptLayout view = (BiometricPromptLayout) layoutInflater.inflate(
-                    R.layout.biometric_prompt_layout, null, false);
-            mBiometricView = BiometricViewBinder.bind(view, viewModel, mPanelController,
-                    // TODO(b/201510778): This uses the wrong timeout in some cases
-                    getJankListener(view, TRANSIT,
-                            BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS),
-                    mBackgroundView, mBiometricCallback, mApplicationCoroutineScope,
-                    vibratorHelper);
+            if (constraintBp()) {
+                mBiometricView = BiometricViewBinder.bind(mLayout, viewModel, null,
+                        // TODO(b/201510778): This uses the wrong timeout in some cases
+                        getJankListener(mLayout, TRANSIT,
+                                BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS),
+                        mBackgroundView, mBiometricCallback, mApplicationCoroutineScope,
+                        vibratorHelper);
+            } else {
+                final BiometricPromptLayout view = (BiometricPromptLayout) layoutInflater.inflate(
+                        R.layout.biometric_prompt_layout, null, false);
+                mBiometricView = BiometricViewBinder.bind(view, viewModel, mPanelController,
+                        // TODO(b/201510778): This uses the wrong timeout in some cases
+                        getJankListener(view, TRANSIT,
+                                BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS),
+                        mBackgroundView, mBiometricCallback, mApplicationCoroutineScope,
+                        vibratorHelper);
 
-            // TODO(b/251476085): migrate these dependencies
-            if (fpProps != null && fpProps.isAnyUdfpsType()) {
-                view.setUdfpsAdapter(new UdfpsDialogMeasureAdapter(view, fpProps),
-                        config.mScaleProvider);
+                // TODO(b/251476085): migrate these dependencies
+                if (fpProps != null && fpProps.isAnyUdfpsType()) {
+                    view.setUdfpsAdapter(new UdfpsDialogMeasureAdapter(view, fpProps),
+                            config.mScaleProvider);
+                }
             }
+        } else if (constraintBp() && Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo)) {
+            addCredentialView(true, false);
         } else {
             mPromptSelectorInteractorProvider.get().resetPrompt();
         }
@@ -477,7 +495,7 @@
         vm.setAnimateContents(animateContents);
         ((CredentialView) mCredentialView).init(vm, this, mPanelController, animatePanel);
 
-        mFrameLayout.addView(mCredentialView);
+        mLayout.addView(mCredentialView);
     }
 
     @Override
@@ -488,7 +506,9 @@
 
     @Override
     public void onOrientationChanged() {
-        maybeUpdatePositionForUdfps(true /* invalidate */);
+        if (!constraintBp()) {
+            maybeUpdatePositionForUdfps(true /* invalidate */);
+        }
     }
 
     @Override
@@ -502,8 +522,9 @@
         mWakefulnessLifecycle.addObserver(this);
         mPanelInteractionDetector.enable(
                 () -> animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED));
-
-        if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) {
+        if (constraintBp()) {
+            // Do nothing on attachment with constraintLayout
+        } else if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) {
             mBiometricScrollView.addView(mBiometricView.asView());
         } else if (Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo)) {
             addCredentialView(true /* animatePanel */, false /* animateContents */);
@@ -512,7 +533,9 @@
                     + mConfig.mPromptInfo.getAuthenticators());
         }
 
-        maybeUpdatePositionForUdfps(false /* invalidate */);
+        if (!constraintBp()) {
+            maybeUpdatePositionForUdfps(false /* invalidate */);
+        }
 
         if (mConfig.mSkipIntro) {
             mContainerState = STATE_SHOWING;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 4176083..4ea5f4c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -198,11 +198,13 @@
                                 UdfpsTouchOverlayBinder.bind(
                                     view = this,
                                     viewModel = deviceEntryUdfpsTouchOverlayViewModel.get(),
+                                    udfpsOverlayInteractor = udfpsOverlayInteractor,
                                 )
                             else ->
                                 UdfpsTouchOverlayBinder.bind(
                                     view = this,
                                     viewModel = defaultUdfpsTouchOverlayViewModel.get(),
+                                    udfpsOverlayInteractor = udfpsOverlayInteractor,
                                 )
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
index d28dbc0..27bb023 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
@@ -24,17 +24,24 @@
 import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS
 import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING
 import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_FIND_SENSOR
+import android.hardware.biometrics.BiometricSourceType
 import com.android.systemui.biometrics.shared.model.AuthenticationReason
 import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations
+import com.android.systemui.biometrics.shared.model.AuthenticationState
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.shareIn
 
 /** A repository for the state of biometric authentication. */
@@ -44,6 +51,9 @@
      * [NotRunning].
      */
     val fingerprintAuthenticationReason: Flow<AuthenticationReason>
+
+    /** The current status of an acquired fingerprint. */
+    val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus>
 }
 
 @SysUISingleton
@@ -54,53 +64,53 @@
     private val biometricManager: BiometricManager?
 ) : BiometricStatusRepository {
 
-    override val fingerprintAuthenticationReason: Flow<AuthenticationReason> =
+    private val authenticationState: Flow<AuthenticationState> =
         conflatedCallbackFlow {
-                val updateFingerprintAuthenticateReason = { reason: AuthenticationReason ->
-                    trySendWithFailureLogging(
-                        reason,
-                        TAG,
-                        "Error sending fingerprintAuthenticateReason reason"
-                    )
+                val updateAuthenticationState = { state: AuthenticationState ->
+                    trySendWithFailureLogging(state, TAG, "Error sending AuthenticationState state")
                 }
 
                 val authenticationStateListener =
                     object : AuthenticationStateListener.Stub() {
                         override fun onAuthenticationStarted(requestReason: Int) {
-                            val authenticationReason =
-                                when (requestReason) {
-                                    REASON_AUTH_BP ->
-                                        AuthenticationReason.BiometricPromptAuthentication
-                                    REASON_AUTH_KEYGUARD ->
-                                        AuthenticationReason.DeviceEntryAuthentication
-                                    REASON_AUTH_OTHER -> AuthenticationReason.OtherAuthentication
-                                    REASON_AUTH_SETTINGS ->
-                                        AuthenticationReason.SettingsAuthentication(
-                                            SettingsOperations.OTHER
-                                        )
-                                    REASON_ENROLL_ENROLLING ->
-                                        AuthenticationReason.SettingsAuthentication(
-                                            SettingsOperations.ENROLL_ENROLLING
-                                        )
-                                    REASON_ENROLL_FIND_SENSOR ->
-                                        AuthenticationReason.SettingsAuthentication(
-                                            SettingsOperations.ENROLL_FIND_SENSOR
-                                        )
-                                    else -> AuthenticationReason.Unknown
-                                }
-                            updateFingerprintAuthenticateReason(authenticationReason)
+                            val authenticationReason = requestReason.toAuthenticationReason()
+                            updateAuthenticationState(
+                                AuthenticationState.AuthenticationStarted(authenticationReason)
+                            )
                         }
 
                         override fun onAuthenticationStopped() {
-                            updateFingerprintAuthenticateReason(AuthenticationReason.NotRunning)
+                            updateAuthenticationState(
+                                AuthenticationState.AuthenticationStopped(
+                                    AuthenticationReason.NotRunning
+                                )
+                            )
                         }
 
                         override fun onAuthenticationSucceeded(requestReason: Int, userId: Int) {}
 
                         override fun onAuthenticationFailed(requestReason: Int, userId: Int) {}
+
+                        override fun onAuthenticationAcquired(
+                            biometricSourceType: BiometricSourceType,
+                            requestReason: Int,
+                            acquiredInfo: Int
+                        ) {
+                            val authReason = requestReason.toAuthenticationReason()
+
+                            updateAuthenticationState(
+                                AuthenticationState.AuthenticationAcquired(
+                                    biometricSourceType,
+                                    authReason,
+                                    acquiredInfo
+                                )
+                            )
+                        }
                     }
 
-                updateFingerprintAuthenticateReason(AuthenticationReason.NotRunning)
+                updateAuthenticationState(
+                    AuthenticationState.AuthenticationStarted(AuthenticationReason.NotRunning)
+                )
                 biometricManager?.registerAuthenticationStateListener(authenticationStateListener)
                 awaitClose {
                     biometricManager?.unregisterAuthenticationStateListener(
@@ -110,7 +120,36 @@
             }
             .shareIn(applicationScope, started = SharingStarted.Eagerly, replay = 1)
 
+    override val fingerprintAuthenticationReason: Flow<AuthenticationReason> =
+        authenticationState.map { it.requestReason }
+
+    override val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus> =
+        authenticationState
+            .filterIsInstance<AuthenticationState.AuthenticationAcquired>()
+            .filter {
+                it.biometricSourceType == BiometricSourceType.FINGERPRINT &&
+                    // TODO(b/322555228) This check will be removed after consolidating device
+                    //  entry auth messages (currently in DeviceEntryFingerprintAuthRepository)
+                    //  with BP auth messages (here)
+                    it.requestReason == AuthenticationReason.BiometricPromptAuthentication
+            }
+            .map { AcquiredFingerprintAuthenticationStatus(it.requestReason, it.acquiredInfo) }
+
     companion object {
         private const val TAG = "BiometricStatusRepositoryImpl"
     }
 }
+
+private fun Int.toAuthenticationReason(): AuthenticationReason =
+    when (this) {
+        REASON_AUTH_BP -> AuthenticationReason.BiometricPromptAuthentication
+        REASON_AUTH_KEYGUARD -> AuthenticationReason.DeviceEntryAuthentication
+        REASON_AUTH_OTHER -> AuthenticationReason.OtherAuthentication
+        REASON_AUTH_SETTINGS ->
+            AuthenticationReason.SettingsAuthentication(SettingsOperations.OTHER)
+        REASON_ENROLL_ENROLLING ->
+            AuthenticationReason.SettingsAuthentication(SettingsOperations.ENROLL_ENROLLING)
+        REASON_ENROLL_FIND_SENSOR ->
+            AuthenticationReason.SettingsAuthentication(SettingsOperations.ENROLL_FIND_SENSOR)
+        else -> AuthenticationReason.Unknown
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt
index 55a2d3d..ed1557c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.biometrics.data.repository.BiometricStatusRepository
 import com.android.systemui.biometrics.shared.model.AuthenticationReason
 import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations
+import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.map
@@ -31,6 +32,9 @@
      * filtered for when the overlay should be shown, otherwise [NotRunning].
      */
     val sfpsAuthenticationReason: Flow<AuthenticationReason>
+
+    /** The current status of an acquired fingerprint. */
+    val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus>
 }
 
 class BiometricStatusInteractorImpl
@@ -50,6 +54,9 @@
             }
         }
 
+    override val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus> =
+        biometricStatusRepository.fingerprintAcquiredStatus
+
     companion object {
         private const val TAG = "BiometricStatusInteractor"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/AuthenticationState.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/AuthenticationState.kt
new file mode 100644
index 0000000..77cf840
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/AuthenticationState.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2024 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.shared.model
+
+import android.hardware.biometrics.BiometricSourceType
+
+/**
+ * Describes the current state of biometric authentication, including whether authentication is
+ * started, stopped, or acquired and relevant parameters, and the [AuthenticationReason] for
+ * authentication.
+ */
+sealed interface AuthenticationState {
+    val requestReason: AuthenticationReason
+
+    /**
+     * Authentication started
+     *
+     * @param requestReason [AuthenticationReason] for starting authentication
+     */
+    data class AuthenticationStarted(override val requestReason: AuthenticationReason) :
+        AuthenticationState
+
+    /**
+     * Authentication stopped
+     *
+     * @param requestReason [AuthenticationReason.NotRunning]
+     */
+    data class AuthenticationStopped(override val requestReason: AuthenticationReason) :
+        AuthenticationState
+
+    /**
+     * Authentication acquired
+     *
+     * @param biometricSourceType indicates [BiometricSourceType] of acquired authentication
+     * @param requestReason indicates [AuthenticationReason] for requesting auth
+     * @param acquiredInfo indicates
+     */
+    data class AuthenticationAcquired(
+        val biometricSourceType: BiometricSourceType,
+        override val requestReason: AuthenticationReason,
+        val acquiredInfo: Int
+    ) : AuthenticationState
+}
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 285ab4a..efad21b 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
@@ -41,6 +41,7 @@
 import androidx.lifecycle.repeatOnLifecycle
 import com.airbnb.lottie.LottieAnimationView
 import com.airbnb.lottie.LottieCompositionFactory
+import com.android.systemui.Flags.constraintBp
 import com.android.systemui.biometrics.AuthPanelController
 import com.android.systemui.biometrics.shared.model.BiometricModalities
 import com.android.systemui.biometrics.shared.model.BiometricModality
@@ -70,9 +71,9 @@
     @SuppressLint("ClickableViewAccessibility")
     @JvmStatic
     fun bind(
-        view: BiometricPromptLayout,
+        view: View,
         viewModel: PromptViewModel,
-        panelViewController: AuthPanelController,
+        panelViewController: AuthPanelController?,
         jankListener: BiometricJankListener,
         backgroundView: View,
         legacyCallback: Spaghetti.Callback,
@@ -112,11 +113,18 @@
         val iconOverlayView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
         val iconView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon)
 
+        val iconSizeOverride =
+            if (constraintBp()) {
+                viewModel.fingerprintAffordanceSize
+            } else {
+                (view as BiometricPromptLayout).updatedFingerprintAffordanceSize
+            }
+
         PromptIconViewBinder.bind(
             iconView,
             iconOverlayView,
-            view.getUpdatedFingerprintAffordanceSize(),
-            viewModel
+            iconSizeOverride,
+            viewModel,
         )
 
         val indicatorMessageView = view.requireViewById<TextView>(R.id.indicator)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index d5695f3..2417fe9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -19,29 +19,45 @@
 import android.animation.Animator
 import android.animation.AnimatorSet
 import android.animation.ValueAnimator
+import android.graphics.Outline
+import android.graphics.Rect
+import android.transition.AutoTransition
+import android.transition.TransitionManager
 import android.view.Surface
 import android.view.View
 import android.view.ViewGroup
+import android.view.ViewOutlineProvider
 import android.view.WindowInsets
 import android.view.WindowManager
 import android.view.accessibility.AccessibilityManager
 import android.widget.ImageView
 import android.widget.TextView
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.Guideline
 import androidx.core.animation.addListener
+import androidx.core.view.doOnAttach
 import androidx.core.view.doOnLayout
 import androidx.core.view.isGone
 import androidx.lifecycle.lifecycleScope
+import com.android.systemui.Flags.constraintBp
 import com.android.systemui.biometrics.AuthPanelController
 import com.android.systemui.biometrics.Utils
-import com.android.systemui.biometrics.ui.BiometricPromptLayout
+import com.android.systemui.biometrics.ui.viewmodel.PromptPosition
 import com.android.systemui.biometrics.ui.viewmodel.PromptSize
 import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
+import com.android.systemui.biometrics.ui.viewmodel.isBottom
 import com.android.systemui.biometrics.ui.viewmodel.isLarge
+import com.android.systemui.biometrics.ui.viewmodel.isLeft
 import com.android.systemui.biometrics.ui.viewmodel.isMedium
 import com.android.systemui.biometrics.ui.viewmodel.isNullOrNotSmall
+import com.android.systemui.biometrics.ui.viewmodel.isRight
 import com.android.systemui.biometrics.ui.viewmodel.isSmall
+import com.android.systemui.biometrics.ui.viewmodel.isTop
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.res.R
+import kotlin.math.abs
+import kotlin.math.min
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
 
@@ -54,18 +70,19 @@
 
     /** Resizes [BiometricPromptLayout] and the [panelViewController] via the [PromptViewModel]. */
     fun bind(
-        view: BiometricPromptLayout,
+        view: View,
         viewModel: PromptViewModel,
         viewsToHideWhenSmall: List<View>,
         viewsToFadeInOnSizeChange: List<View>,
-        panelViewController: AuthPanelController,
+        panelViewController: AuthPanelController?,
         jankListener: BiometricJankListener,
     ) {
         val windowManager = requireNotNull(view.context.getSystemService(WindowManager::class.java))
         val accessibilityManager =
             requireNotNull(view.context.getSystemService(AccessibilityManager::class.java))
+
         fun notifyAccessibilityChanged() {
-            Utils.notifyAccessibilityContentChanged(accessibilityManager, view)
+            Utils.notifyAccessibilityContentChanged(accessibilityManager, view as ViewGroup)
         }
 
         fun startMonitoredAnimation(animators: List<Animator>) {
@@ -77,149 +94,342 @@
             }
         }
 
-        val iconHolderView = view.requireViewById<View>(R.id.biometric_icon_frame)
-        val iconPadding = view.resources.getDimension(R.dimen.biometric_dialog_icon_padding)
-        val fullSizeYOffset =
-            view.resources.getDimension(R.dimen.biometric_dialog_medium_to_large_translation_offset)
+        if (constraintBp()) {
+            val leftGuideline = view.requireViewById<Guideline>(R.id.leftGuideline)
+            val rightGuideline = view.requireViewById<Guideline>(R.id.rightGuideline)
+            val bottomGuideline = view.requireViewById<Guideline>(R.id.bottomGuideline)
 
-        // cache the original position of the icon view (as done in legacy view)
-        // this must happen before any size changes can be made
-        view.doOnLayout {
-            // TODO(b/251476085): this old way of positioning has proven itself unreliable
-            // remove this and associated thing like (UdfpsDialogMeasureAdapter) and
-            // pin to the physical sensor
-            val iconHolderOriginalY = iconHolderView.y
+            val iconHolderView = view.requireViewById<View>(R.id.biometric_icon)
+            val panelView = view.requireViewById<View>(R.id.panel)
+            val cornerRadius = view.resources.getDimension(R.dimen.biometric_dialog_corner_size)
 
-            // bind to prompt
-            // TODO(b/251476085): migrate the legacy panel controller and simplify this
-            view.repeatWhenAttached {
-                var currentSize: PromptSize? = null
-                lifecycleScope.launch {
-                    /**
-                     * View is only set visible in BiometricViewSizeBinder once PromptSize is
-                     * determined that accounts for iconView size, to prevent prompt resizing being
-                     * visible to the user.
-                     *
-                     * TODO(b/288175072): May be able to remove isIconViewLoaded once constraint
-                     *   layout is implemented
-                     */
-                    combine(viewModel.isIconViewLoaded, viewModel.size, ::Pair).collect {
-                        (isIconViewLoaded, size) ->
-                        if (!isIconViewLoaded) {
-                            return@collect
+            // ConstraintSets for animating between prompt sizes
+            val mediumConstraintSet = ConstraintSet()
+            mediumConstraintSet.clone(view as ConstraintLayout)
+
+            val smallConstraintSet = ConstraintSet()
+            smallConstraintSet.clone(mediumConstraintSet)
+            viewsToHideWhenSmall.forEach { smallConstraintSet.setVisibility(it.id, View.GONE) }
+
+            val largeConstraintSet = ConstraintSet()
+            largeConstraintSet.clone(mediumConstraintSet)
+            viewsToHideWhenSmall.forEach { largeConstraintSet.setVisibility(it.id, View.GONE) }
+            largeConstraintSet.setVisibility(iconHolderView.id, View.GONE)
+            largeConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE)
+            largeConstraintSet.setVisibility(R.id.indicator, View.GONE)
+            largeConstraintSet.setGuidelineBegin(leftGuideline.id, 0)
+            largeConstraintSet.setGuidelineEnd(rightGuideline.id, 0)
+            largeConstraintSet.setGuidelineEnd(bottomGuideline.id, 0)
+
+            // Round the panel outline
+            panelView.outlineProvider =
+                object : ViewOutlineProvider() {
+                    override fun getOutline(view: View, outline: Outline) {
+                        outline.setRoundRect(0, 0, view.width, view.height, cornerRadius)
+                    }
+                }
+
+            view.doOnLayout {
+                val windowBounds = windowManager.maximumWindowMetrics.bounds
+                val bottomInset =
+                    windowManager.maximumWindowMetrics.windowInsets
+                        .getInsets(WindowInsets.Type.navigationBars())
+                        .bottom
+
+                fun measureBounds(position: PromptPosition) {
+                    val width = min(windowBounds.height(), windowBounds.width())
+
+                    var left = -1
+                    var top = -1
+                    var right = -1
+                    var bottom = -1
+
+                    when {
+                        position.isTop -> {
+                            left = windowBounds.centerX() - width / 2 + viewModel.promptMargin
+                            top = viewModel.promptMargin
+                            right = windowBounds.centerX() - width / 2 + viewModel.promptMargin
+                            bottom = iconHolderView.centerY() * 2 - iconHolderView.centerY() / 4
+                        }
+                        position.isBottom -> {
+                            if (view.isLandscape()) {
+                                left = windowBounds.centerX() - width / 2 + viewModel.promptMargin
+                                top = iconHolderView.centerY()
+                                right = windowBounds.centerX() - width / 2 + viewModel.promptMargin
+                                bottom = bottomInset + viewModel.promptMargin
+                            } else {
+                                left = windowBounds.centerX() - width / 2 + viewModel.promptMargin
+                                top =
+                                    windowBounds.height() -
+                                        (windowBounds.height() - iconHolderView.centerY()) * 2 +
+                                        viewModel.promptMargin
+                                right = windowBounds.centerX() - width / 2 + viewModel.promptMargin
+                                bottom = viewModel.promptMargin
+                            }
                         }
 
-                        // prepare for animated size transitions
-                        for (v in viewsToHideWhenSmall) {
-                            v.showContentOrHide(forceHide = size.isSmall)
+                        // For Udfps exclusive left and right, measure guideline to center
+                        // icon in BP
+                        position.isLeft -> {
+                            left = viewModel.promptMargin
+                            top =
+                                windowBounds.height() -
+                                    (windowBounds.height() - iconHolderView.centerY()) * 2 +
+                                    viewModel.promptMargin
+                            right =
+                                abs(
+                                    windowBounds.width() - iconHolderView.centerX() * 2 +
+                                        viewModel.promptMargin
+                                )
+                            bottom = bottomInset + viewModel.promptMargin
                         }
-                        if (currentSize == null && size.isSmall) {
-                            iconHolderView.alpha = 0f
+                        position.isRight -> {
+                            left =
+                                abs(
+                                    iconHolderView.centerX() -
+                                        (windowBounds.width() - iconHolderView.centerX()) -
+                                        viewModel.promptMargin
+                                )
+                            top =
+                                windowBounds.height() -
+                                    (windowBounds.height() - iconHolderView.centerY()) * 2 +
+                                    viewModel.promptMargin
+                            right = viewModel.promptMargin
+                            bottom = bottomInset + viewModel.promptMargin
                         }
-                        if ((currentSize.isSmall && size.isMedium) || size.isSmall) {
-                            viewsToFadeInOnSizeChange.forEach { it.alpha = 0f }
-                        }
+                    }
 
-                        // TODO(b/302735104): Fix wrong height due to the delay of
-                        // PromptContentView. addOnLayoutChangeListener() will cause crash when
-                        // showing credential view, since |PromptIconViewModel| won't release the
-                        // flow.
-                        // propagate size changes to legacy panel controller and animate transitions
-                        view.doOnLayout {
-                            val width = view.measuredWidth
-                            val height = view.measuredHeight
+                    val bounds = Rect(left, top, right, bottom)
+                    if (bounds.shouldAdjustLeftGuideline()) {
+                        leftGuideline.setGuidelineBegin(bounds.left)
+                        smallConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left)
+                        mediumConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left)
+                    }
+                    if (bounds.shouldAdjustRightGuideline()) {
+                        rightGuideline.setGuidelineEnd(bounds.right)
+                        smallConstraintSet.setGuidelineEnd(rightGuideline.id, bounds.right)
+                        mediumConstraintSet.setGuidelineEnd(rightGuideline.id, bounds.right)
+                    }
+                    if (bounds.shouldAdjustBottomGuideline()) {
+                        bottomGuideline.setGuidelineEnd(bounds.bottom)
+                        smallConstraintSet.setGuidelineEnd(bottomGuideline.id, bounds.bottom)
+                        mediumConstraintSet.setGuidelineEnd(bottomGuideline.id, bounds.bottom)
+                    }
+                }
 
-                            when {
-                                size.isSmall -> {
-                                    iconHolderView.alpha = 1f
-                                    val bottomInset =
-                                        windowManager.maximumWindowMetrics.windowInsets
-                                            .getInsets(WindowInsets.Type.navigationBars())
-                                            .bottom
-                                    iconHolderView.y =
-                                        if (view.isLandscape()) {
-                                            (view.height - iconHolderView.height - bottomInset) / 2f
-                                        } else {
-                                            view.height -
-                                                iconHolderView.height -
-                                                iconPadding -
-                                                bottomInset
-                                        }
-                                    val newHeight =
-                                        iconHolderView.height + (2 * iconPadding.toInt()) -
-                                            iconHolderView.paddingTop -
-                                            iconHolderView.paddingBottom
-                                    panelViewController.updateForContentDimensions(
-                                        width,
-                                        newHeight + bottomInset,
-                                        0, /* animateDurationMs */
-                                    )
-                                }
-                                size.isMedium && currentSize.isSmall -> {
-                                    val duration = ANIMATE_SMALL_TO_MEDIUM_DURATION_MS
-                                    panelViewController.updateForContentDimensions(
-                                        width,
-                                        height,
-                                        duration,
-                                    )
-                                    startMonitoredAnimation(
-                                        listOf(
-                                            iconHolderView.asVerticalAnimator(
-                                                duration = duration.toLong(),
-                                                toY =
-                                                    iconHolderOriginalY -
-                                                        viewsToHideWhenSmall
-                                                            .filter { it.isGone }
-                                                            .sumOf { it.height },
-                                            ),
-                                            viewsToFadeInOnSizeChange.asFadeInAnimator(
-                                                duration = duration.toLong(),
-                                                delay = duration.toLong(),
-                                            ),
+                view.repeatWhenAttached {
+                    var currentSize: PromptSize? = null
+                    lifecycleScope.launch {
+                        combine(viewModel.position, viewModel.size, ::Pair).collect {
+                            (position, size) ->
+                            view.doOnAttach {
+                                measureBounds(position)
+
+                                when {
+                                    size.isSmall -> {
+                                        val ratio =
+                                            if (view.isLandscape()) {
+                                                (windowBounds.height() -
+                                                        bottomInset -
+                                                        viewModel.promptMargin)
+                                                    .toFloat() / windowBounds.height()
+                                            } else {
+                                                (windowBounds.height() - viewModel.promptMargin)
+                                                    .toFloat() / windowBounds.height()
+                                            }
+                                        smallConstraintSet.setVerticalBias(iconHolderView.id, ratio)
+
+                                        smallConstraintSet.applyTo(view as ConstraintLayout?)
+                                    }
+                                    size.isMedium && currentSize.isSmall -> {
+                                        val autoTransition = AutoTransition()
+                                        autoTransition.setDuration(
+                                            ANIMATE_SMALL_TO_MEDIUM_DURATION_MS.toLong()
                                         )
-                                    )
-                                }
-                                size.isMedium && currentSize.isNullOrNotSmall -> {
-                                    panelViewController.updateForContentDimensions(
-                                        width,
-                                        height,
-                                        0, /* animateDurationMs */
-                                    )
-                                }
-                                size.isLarge -> {
-                                    val duration = ANIMATE_MEDIUM_TO_LARGE_DURATION_MS
-                                    panelViewController.setUseFullScreen(true)
-                                    panelViewController.updateForContentDimensions(
-                                        panelViewController.containerWidth,
-                                        panelViewController.containerHeight,
-                                        duration,
-                                    )
 
-                                    startMonitoredAnimation(
-                                        listOf(
-                                            view.asVerticalAnimator(
-                                                duration.toLong() * 2 / 3,
-                                                toY = view.y - fullSizeYOffset
-                                            ),
-                                            listOf(view)
-                                                .asFadeInAnimator(
-                                                    duration = duration.toLong() / 2,
-                                                    delay = duration.toLong(),
-                                                ),
+                                        TransitionManager.beginDelayedTransition(
+                                            view,
+                                            autoTransition
                                         )
-                                    )
-                                    // TODO(b/251476085): clean up (copied from legacy)
-                                    if (view.isAttachedToWindow) {
-                                        val parent = view.parent as? ViewGroup
-                                        parent?.removeView(view)
+                                        mediumConstraintSet.applyTo(view)
+                                    }
+                                    size.isLarge -> {
+                                        val autoTransition = AutoTransition()
+                                        autoTransition.setDuration(
+                                            ANIMATE_MEDIUM_TO_LARGE_DURATION_MS.toLong()
+                                        )
+
+                                        TransitionManager.beginDelayedTransition(
+                                            view,
+                                            autoTransition
+                                        )
+                                        largeConstraintSet.applyTo(view)
                                     }
                                 }
+
+                                currentSize = size
+                                view.visibility = View.VISIBLE
+                                viewModel.setIsIconViewLoaded(false)
+                                notifyAccessibilityChanged()
+
+                                view.invalidate()
+                                view.requestLayout()
+                            }
+                        }
+                    }
+                }
+            }
+        } else if (panelViewController != null) {
+            val iconHolderView = view.requireViewById<View>(R.id.biometric_icon_frame)
+            val iconPadding = view.resources.getDimension(R.dimen.biometric_dialog_icon_padding)
+            val fullSizeYOffset =
+                view.resources.getDimension(
+                    R.dimen.biometric_dialog_medium_to_large_translation_offset
+                )
+
+            // cache the original position of the icon view (as done in legacy view)
+            // this must happen before any size changes can be made
+            view.doOnLayout {
+                // TODO(b/251476085): this old way of positioning has proven itself unreliable
+                // remove this and associated thing like (UdfpsDialogMeasureAdapter) and
+                // pin to the physical sensor
+                val iconHolderOriginalY = iconHolderView.y
+
+                // bind to prompt
+                // TODO(b/251476085): migrate the legacy panel controller and simplify this
+                view.repeatWhenAttached {
+                    var currentSize: PromptSize? = null
+                    lifecycleScope.launch {
+                        /**
+                         * View is only set visible in BiometricViewSizeBinder once PromptSize is
+                         * determined that accounts for iconView size, to prevent prompt resizing
+                         * being visible to the user.
+                         *
+                         * TODO(b/288175072): May be able to remove isIconViewLoaded once constraint
+                         *   layout is implemented
+                         */
+                        combine(viewModel.isIconViewLoaded, viewModel.size, ::Pair).collect {
+                            (isIconViewLoaded, size) ->
+                            if (!isIconViewLoaded) {
+                                return@collect
                             }
 
-                            currentSize = size
-                            view.visibility = View.VISIBLE
-                            viewModel.setIsIconViewLoaded(false)
-                            notifyAccessibilityChanged()
+                            // prepare for animated size transitions
+                            for (v in viewsToHideWhenSmall) {
+                                v.showContentOrHide(forceHide = size.isSmall)
+                            }
+                            if (currentSize == null && size.isSmall) {
+                                iconHolderView.alpha = 0f
+                            }
+                            if ((currentSize.isSmall && size.isMedium) || size.isSmall) {
+                                viewsToFadeInOnSizeChange.forEach { it.alpha = 0f }
+                            }
+
+                            // TODO(b/302735104): Fix wrong height due to the delay of
+                            // PromptContentView. addOnLayoutChangeListener() will cause crash when
+                            // showing credential view, since |PromptIconViewModel| won't release
+                            // the
+                            // flow.
+                            // propagate size changes to legacy panel controller and animate
+                            // transitions
+                            view.doOnLayout {
+                                val width = view.measuredWidth
+                                val height = view.measuredHeight
+
+                                when {
+                                    size.isSmall -> {
+                                        iconHolderView.alpha = 1f
+                                        val bottomInset =
+                                            windowManager.maximumWindowMetrics.windowInsets
+                                                .getInsets(WindowInsets.Type.navigationBars())
+                                                .bottom
+                                        iconHolderView.y =
+                                            if (view.isLandscape()) {
+                                                (view.height -
+                                                    iconHolderView.height -
+                                                    bottomInset) / 2f
+                                            } else {
+                                                view.height -
+                                                    iconHolderView.height -
+                                                    iconPadding -
+                                                    bottomInset
+                                            }
+                                        val newHeight =
+                                            iconHolderView.height + (2 * iconPadding.toInt()) -
+                                                iconHolderView.paddingTop -
+                                                iconHolderView.paddingBottom
+                                        panelViewController.updateForContentDimensions(
+                                            width,
+                                            newHeight + bottomInset,
+                                            0, /* animateDurationMs */
+                                        )
+                                    }
+                                    size.isMedium && currentSize.isSmall -> {
+                                        val duration = ANIMATE_SMALL_TO_MEDIUM_DURATION_MS
+                                        panelViewController.updateForContentDimensions(
+                                            width,
+                                            height,
+                                            duration,
+                                        )
+                                        startMonitoredAnimation(
+                                            listOf(
+                                                iconHolderView.asVerticalAnimator(
+                                                    duration = duration.toLong(),
+                                                    toY =
+                                                        iconHolderOriginalY -
+                                                            viewsToHideWhenSmall
+                                                                .filter { it.isGone }
+                                                                .sumOf { it.height },
+                                                ),
+                                                viewsToFadeInOnSizeChange.asFadeInAnimator(
+                                                    duration = duration.toLong(),
+                                                    delay = duration.toLong(),
+                                                ),
+                                            )
+                                        )
+                                    }
+                                    size.isMedium && currentSize.isNullOrNotSmall -> {
+                                        panelViewController.updateForContentDimensions(
+                                            width,
+                                            height,
+                                            0, /* animateDurationMs */
+                                        )
+                                    }
+                                    size.isLarge -> {
+                                        val duration = ANIMATE_MEDIUM_TO_LARGE_DURATION_MS
+                                        panelViewController.setUseFullScreen(true)
+                                        panelViewController.updateForContentDimensions(
+                                            panelViewController.containerWidth,
+                                            panelViewController.containerHeight,
+                                            duration,
+                                        )
+
+                                        startMonitoredAnimation(
+                                            listOf(
+                                                view.asVerticalAnimator(
+                                                    duration.toLong() * 2 / 3,
+                                                    toY = view.y - fullSizeYOffset
+                                                ),
+                                                listOf(view)
+                                                    .asFadeInAnimator(
+                                                        duration = duration.toLong() / 2,
+                                                        delay = duration.toLong(),
+                                                    ),
+                                            )
+                                        )
+                                        // TODO(b/251476085): clean up (copied from legacy)
+                                        if (view.isAttachedToWindow) {
+                                            val parent = view.parent as? ViewGroup
+                                            parent?.removeView(view)
+                                        }
+                                    }
+                                }
+
+                                currentSize = size
+                                view.visibility = View.VISIBLE
+                                viewModel.setIsIconViewLoaded(false)
+                                notifyAccessibilityChanged()
+                            }
                         }
                     }
                 }
@@ -244,6 +454,20 @@
         }
 }
 
+private fun View.centerX(): Int {
+    return (x + width / 2).toInt()
+}
+
+private fun View.centerY(): Int {
+    return (y + height / 2).toInt()
+}
+
+private fun Rect.shouldAdjustLeftGuideline(): Boolean = left != -1
+
+private fun Rect.shouldAdjustRightGuideline(): Boolean = right != -1
+
+private fun Rect.shouldAdjustBottomGuideline(): Boolean = bottom != -1
+
 private fun View.asVerticalAnimator(
     duration: Long,
     toY: Float,
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
index 6e3bcf5..2e47375 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
@@ -17,13 +17,17 @@
 
 package com.android.systemui.biometrics.ui.binder
 
+import android.graphics.Rect
 import android.graphics.drawable.Animatable2
 import android.graphics.drawable.AnimatedVectorDrawable
 import android.graphics.drawable.Drawable
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.airbnb.lottie.LottieAnimationView
 import com.android.settingslib.widget.LottieColorUtils
+import com.android.systemui.Flags.constraintBp
 import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel
 import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel.AuthType
 import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
@@ -119,6 +123,24 @@
                 }
 
                 launch {
+                    viewModel.iconPosition.collect { position ->
+                        if (constraintBp() && position != Rect()) {
+                            val iconParams = iconView.layoutParams as ConstraintLayout.LayoutParams
+
+                            if (position.left != -1) {
+                                iconParams.endToEnd = ConstraintSet.UNSET
+                                iconParams.leftMargin = position.left
+                            }
+                            if (position.top != -1) {
+                                iconParams.bottomToBottom = ConstraintSet.UNSET
+                                iconParams.topMargin = position.top
+                            }
+                            iconView.layoutParams = iconParams
+                        }
+                    }
+                }
+
+                launch {
                     viewModel.iconAsset
                         .sample(
                             combine(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
index 80d37b4..7b4be02 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
@@ -50,10 +50,12 @@
 import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
 
 /** Binds the side fingerprint sensor indicator view to [SideFpsOverlayViewModel]. */
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class SideFpsOverlayViewBinder
 @Inject
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt
index bb6a68b..2e29c3b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt
@@ -16,9 +16,11 @@
 
 package com.android.systemui.biometrics.ui.binder
 
+import android.util.Log
 import androidx.core.view.isInvisible
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.biometrics.ui.view.UdfpsTouchOverlay
 import com.android.systemui.biometrics.ui.viewmodel.UdfpsTouchOverlayViewModel
 import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
@@ -31,19 +33,26 @@
 
     /**
      * Updates visibility for the UdfpsTouchOverlay which controls whether the view will receive
-     * touches or not.
+     * touches or not. For some devices, this is instead handled by UdfpsOverlayInteractor, so this
+     * viewBinder will send the information to the interactor.
      */
     @JvmStatic
     fun bind(
         view: UdfpsTouchOverlay,
         viewModel: UdfpsTouchOverlayViewModel,
+        udfpsOverlayInteractor: UdfpsOverlayInteractor,
     ) {
         if (DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()) return
         view.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.CREATED) {
                 launch {
                     viewModel.shouldHandleTouches.collect { shouldHandleTouches ->
+                        Log.d(
+                            "UdfpsTouchOverlayBinder",
+                            "[$view]: update shouldHandleTouches=$shouldHandleTouches"
+                        )
                         view.isInvisible = !shouldHandleTouches
+                        udfpsOverlayInteractor.setHandleTouches(shouldHandleTouches)
                     }
                 }
             }
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
index 3defec5..b7cffaf 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
@@ -20,8 +20,11 @@
 import android.annotation.DrawableRes
 import android.annotation.RawRes
 import android.content.res.Configuration
+import android.graphics.Rect
+import android.util.RotationUtils
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.biometrics.shared.model.DisplayRotation
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.res.R
@@ -42,7 +45,8 @@
 constructor(
     promptViewModel: PromptViewModel,
     private val displayStateInteractor: DisplayStateInteractor,
-    promptSelectorInteractor: PromptSelectorInteractor
+    promptSelectorInteractor: PromptSelectorInteractor,
+    udfpsOverlayInteractor: UdfpsOverlayInteractor,
 ) {
 
     /** Auth types for the UI to display. */
@@ -71,7 +75,40 @@
             } else if (modalities.hasFingerprintOnly) {
                 AuthType.Fingerprint
             } else {
-                throw IllegalStateException("unexpected modality: $modalities")
+                // TODO(b/288175072): Remove, currently needed for transition to credential view
+                AuthType.Fingerprint
+            }
+        }
+
+    val udfpsSensorBounds: Flow<Rect> =
+        combine(
+                udfpsOverlayInteractor.udfpsOverlayParams,
+                displayStateInteractor.currentRotation
+            ) { params, rotation ->
+                val rotatedBounds = Rect(params.sensorBounds)
+                RotationUtils.rotateBounds(
+                    rotatedBounds,
+                    params.naturalDisplayWidth,
+                    params.naturalDisplayHeight,
+                    rotation.ordinal
+                )
+                rotatedBounds
+            }
+            .distinctUntilChanged()
+
+    val iconPosition: Flow<Rect> =
+        combine(udfpsSensorBounds, promptViewModel.size, promptViewModel.modalities) {
+            sensorBounds,
+            size,
+            modalities ->
+            // If not Udfps, icon does not change from default layout position
+            if (!modalities.hasUdfps) {
+                Rect() // Empty rect, don't offset from default position
+            } else if (size.isSmall) {
+                // When small with Udfps, only set horizontal position
+                Rect(sensorBounds.left, -1, sensorBounds.right, -1)
+            } else {
+                sensorBounds
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptPosition.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptPosition.kt
new file mode 100644
index 0000000..d45dad6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptPosition.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 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
+
+/** The position of a biometric prompt */
+enum class PromptPosition {
+    Top,
+    Bottom,
+    Left,
+    Right,
+}
+
+val PromptPosition?.isBottom: Boolean
+    get() = this != null && this == PromptPosition.Bottom
+
+val PromptPosition?.isLeft: Boolean
+    get() = this != null && this == PromptPosition.Left
+
+val PromptPosition?.isRight: Boolean
+    get() = this != null && this == PromptPosition.Right
+
+val PromptPosition?.isTop: Boolean
+    get() = this != null && this == PromptPosition.Top
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 0f1340a..ef5c37ea 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
@@ -17,6 +17,7 @@
 package com.android.systemui.biometrics.ui.viewmodel
 
 import android.content.Context
+import android.content.pm.PackageManager
 import android.graphics.Rect
 import android.graphics.drawable.BitmapDrawable
 import android.graphics.drawable.Drawable
@@ -81,11 +82,23 @@
     val faceIconHeight: Int =
         context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
 
+    val fingerprintSensorDiameter: Int =
+        (udfpsOverlayInteractor.udfpsOverlayParams.value.sensorBounds.width() *
+                udfpsOverlayInteractor.udfpsOverlayParams.value.scaleFactor)
+            .toInt()
+    val fingerprintAffordanceSize: Pair<Int, Int>? =
+        if (fingerprintSensorDiameter != 0)
+            Pair(fingerprintSensorDiameter, fingerprintSensorDiameter)
+        else null
+
     private val _accessibilityHint = MutableSharedFlow<String>()
 
     /** Hint for talkback directional guidance */
     val accessibilityHint: Flow<String> = _accessibilityHint.asSharedFlow()
 
+    val promptMargin: Int =
+        context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_border_padding)
+
     private val _isAuthenticating: MutableStateFlow<Boolean> = MutableStateFlow(false)
 
     /** If the user is currently authenticating (i.e. at least one biometric is scanning). */
@@ -135,6 +148,22 @@
     /** Event fired to the view indicating a [HapticFeedbackConstants] to be played */
     val hapticsToPlay = _hapticsToPlay.asStateFlow()
 
+    /** The current position of the prompt */
+    val position: Flow<PromptPosition> =
+        combine(_forceLargeSize, modalities, displayStateInteractor.currentRotation) {
+                forceLarge,
+                modalities,
+                rotation ->
+                when {
+                    forceLarge || !modalities.hasUdfps -> PromptPosition.Bottom
+                    rotation == DisplayRotation.ROTATION_90 -> PromptPosition.Right
+                    rotation == DisplayRotation.ROTATION_270 -> PromptPosition.Left
+                    rotation == DisplayRotation.ROTATION_180 -> PromptPosition.Top
+                    else -> PromptPosition.Bottom
+                }
+            }
+            .distinctUntilChanged()
+
     /** The size of the prompt. */
     val size: Flow<PromptSize> =
         combine(
@@ -195,7 +224,12 @@
             .distinctUntilChanged()
 
     val iconViewModel: PromptIconViewModel =
-        PromptIconViewModel(this, displayStateInteractor, promptSelectorInteractor)
+        PromptIconViewModel(
+            this,
+            displayStateInteractor,
+            promptSelectorInteractor,
+            udfpsOverlayInteractor
+        )
 
     private val _isIconViewLoaded = MutableStateFlow(false)
 
@@ -244,7 +278,13 @@
                     !customBiometricPrompt() || it == null -> null
                     it.logoRes != -1 -> context.resources.getDrawable(it.logoRes, context.theme)
                     it.logoBitmap != null -> BitmapDrawable(context.resources, it.logoBitmap)
-                    else -> context.packageManager.getApplicationIcon(it.opPackageName)
+                    else ->
+                        try {
+                            context.packageManager.getApplicationIcon(it.opPackageName)
+                        } catch (e: PackageManager.NameNotFoundException) {
+                            Log.w(TAG, "Cannot find icon for package " + it.opPackageName, e)
+                            null
+                        }
                 }
             }
             .distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
index ce72603..cfda75c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
@@ -41,12 +41,14 @@
 import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
 import com.android.systemui.res.R
 import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 
 /** Models UI of the side fingerprint sensor indicator view. */
+@OptIn(ExperimentalCoroutinesApi::class)
 class SideFpsOverlayViewModel
 @Inject
 constructor(
@@ -176,8 +178,8 @@
     val lottieCallbacks: Flow<List<LottieCallback>> =
         combine(
             biometricStatusInteractor.sfpsAuthenticationReason,
-            deviceEntrySideFpsOverlayInteractor.showIndicatorForDeviceEntry.distinctUntilChanged(),
-            sideFpsProgressBarViewModel.isVisible,
+            deviceEntrySideFpsOverlayInteractor.showIndicatorForDeviceEntry,
+            sideFpsProgressBarViewModel.isVisible
         ) { reason: AuthenticationReason, showIndicatorForDeviceEntry: Boolean, progressBarIsVisible
             ->
             val callbacks = mutableListOf<LottieCallback>()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
index c46f0d1..33edb80 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
@@ -21,21 +21,21 @@
 
 /** Data model of media on the communal hub. */
 data class CommunalMediaModel(
-    val hasAnyMediaOrRecommendation: Boolean,
+    val hasActiveMediaOrRecommendation: Boolean,
     val createdTimestampMillis: Long = 0L,
 ) : Diffable<CommunalMediaModel> {
     companion object {
         val INACTIVE =
             CommunalMediaModel(
-                hasAnyMediaOrRecommendation = false,
+                hasActiveMediaOrRecommendation = false,
             )
     }
 
     override fun logDiffs(prevVal: CommunalMediaModel, row: TableRowLogger) {
-        if (hasAnyMediaOrRecommendation != prevVal.hasAnyMediaOrRecommendation) {
+        if (hasActiveMediaOrRecommendation != prevVal.hasActiveMediaOrRecommendation) {
             row.logChange(
                 columnName = "isMediaActive",
-                value = hasAnyMediaOrRecommendation,
+                value = hasActiveMediaOrRecommendation,
             )
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
index 2b66491..201be51 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
@@ -73,10 +73,10 @@
         )
 
     private fun updateMediaModel(data: MediaData? = null) {
-        if (mediaDataManager.hasAnyMediaOrRecommendation()) {
+        if (mediaDataManager.hasActiveMediaOrRecommendation()) {
             _mediaModel.value =
                 CommunalMediaModel(
-                    hasAnyMediaOrRecommendation = true,
+                    hasActiveMediaOrRecommendation = true,
                     createdTimestampMillis = data?.createdTimestampMillis ?: 0L,
                 )
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
index 85aeb4d..0e9b32f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
@@ -28,8 +28,8 @@
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.settings.UserFileManager
-import com.android.systemui.settings.UserFileManagerExt.observeSharedPreferences
 import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
@@ -97,8 +97,8 @@
         }
 
     private fun observeCtaDismissState(user: UserInfo): Flow<Boolean> =
-        userFileManager
-            .observeSharedPreferences(FILE_NAME, Context.MODE_PRIVATE, user.id)
+        getSharedPrefsForUser(user)
+            .observe(CTA_DISMISSED_STATE)
             // Emit at the start of collection to ensure we get an initial value
             .onStart { emit(Unit) }
             .map { getCtaDismissedState() }
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 75a27a2..950ac3c 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
@@ -318,7 +318,7 @@
             )
 
             // Add UMO
-            if (media.hasAnyMediaOrRecommendation) {
+            if (media.hasActiveMediaOrRecommendation) {
                 ongoingContent.add(
                     CommunalContentModel.Umo(
                         createdTimestampMillis = media.createdTimestampMillis,
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 0c12841..40d2d16 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
@@ -87,7 +87,7 @@
         with(mediaHost) {
             expansion = MediaHostState.EXPANDED
             expandedMatchesParentHeight = true
-            showsOnlyActiveMedia = false
+            showsOnlyActiveMedia = true
             falsingProtectionNeeded = false
             init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt
index ae9c37a..b35bec4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt
@@ -17,11 +17,16 @@
 
 package com.android.systemui.controls.panels
 
+import android.os.UserHandle
+import kotlinx.coroutines.flow.Flow
+
 /**
  * Repository for keeping track of which packages the panel has authorized to show control panels
  * (embedded activity).
  */
 interface AuthorizedPanelsRepository {
+    /** Exposes the authorized panels as a [Flow] for subscribing to updates */
+    fun observeAuthorizedPanels(user: UserHandle): Flow<Set<String>>
 
     /** A set of package names that the user has previously authorized to show panels. */
     fun getAuthorizedPanels(): Set<String>
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
index 4e935df..7c2dae3 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
@@ -19,11 +19,16 @@
 
 import android.content.Context
 import android.content.SharedPreferences
+import android.os.UserHandle
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
+import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
 import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
 
 class AuthorizedPanelsRepositoryImpl
 @Inject
@@ -33,19 +38,24 @@
     private val userTracker: UserTracker,
 ) : AuthorizedPanelsRepository {
 
+    override fun observeAuthorizedPanels(user: UserHandle): Flow<Set<String>> {
+        val prefs = instantiateSharedPrefs(user)
+        return prefs.observe(KEY).onStart { emit(Unit) }.map { getAuthorizedPanelsInternal(prefs) }
+    }
+
     override fun getAuthorizedPanels(): Set<String> {
-        return getAuthorizedPanelsInternal(instantiateSharedPrefs())
+        return getAuthorizedPanelsInternal(instantiateSharedPrefs(userTracker.userHandle))
     }
 
     override fun getPreferredPackages(): Set<String> =
         context.resources.getStringArray(R.array.config_controlsPreferredPackages).toSet()
 
     override fun addAuthorizedPanels(packageNames: Set<String>) {
-        addAuthorizedPanelsInternal(instantiateSharedPrefs(), packageNames)
+        addAuthorizedPanelsInternal(instantiateSharedPrefs(userTracker.userHandle), packageNames)
     }
 
     override fun removeAuthorizedPanels(packageNames: Set<String>) {
-        with(instantiateSharedPrefs()) {
+        with(instantiateSharedPrefs(userTracker.userHandle)) {
             val currentSet = getAuthorizedPanelsInternal(this)
             edit().putStringSet(KEY, currentSet - packageNames).apply()
         }
@@ -63,12 +73,12 @@
         sharedPreferences.edit().putStringSet(KEY, currentSet + packageNames).apply()
     }
 
-    private fun instantiateSharedPrefs(): SharedPreferences {
+    private fun instantiateSharedPrefs(user: UserHandle): SharedPreferences {
         val sharedPref =
             userFileManager.getSharedPreferences(
                 DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
                 Context.MODE_PRIVATE,
-                userTracker.userId,
+                user.identifier,
             )
 
         // We should add default packages when we've never run this
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
index 0baa81a..9be04940 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
@@ -20,21 +20,18 @@
 import android.content.Context
 import android.content.SharedPreferences
 import android.os.UserHandle
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
+import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
 
 @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
 @SysUISingleton
@@ -43,9 +40,7 @@
 constructor(
     private val userFileManager: UserFileManager,
     private val userTracker: UserTracker,
-    private val featureFlags: FeatureFlags,
-    @Background private val bgDispatcher: CoroutineDispatcher,
-    @Application private val applicationScope: CoroutineScope
+    @Background private val bgDispatcher: CoroutineDispatcher
 ) : SelectedComponentRepository {
 
     private companion object {
@@ -66,22 +61,11 @@
     override fun selectedComponentFlow(
         userHandle: UserHandle
     ): Flow<SelectedComponentRepository.SelectedComponent?> {
-        return conflatedCallbackFlow {
-                val sharedPreferencesByUserId = getSharedPreferencesForUser(userHandle.identifier)
-                val listener =
-                    SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
-                        applicationScope.launch(bgDispatcher) {
-                            if (key == PREF_COMPONENT) {
-                                trySend(getSelectedComponent(userHandle))
-                            }
-                        }
-                    }
-                sharedPreferencesByUserId.registerOnSharedPreferenceChangeListener(listener)
-                send(getSelectedComponent(userHandle))
-                awaitClose {
-                    sharedPreferencesByUserId.unregisterOnSharedPreferenceChangeListener(listener)
-                }
-            }
+        val prefs = getSharedPreferencesForUser(userHandle.identifier)
+        return prefs
+            .observe(PREF_COMPONENT)
+            .onStart { emit(Unit) }
+            .map { getSelectedComponent(userHandle) }
             .flowOn(bgDispatcher)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index e9d1e94..dd186d6 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -42,7 +42,7 @@
 import com.android.systemui.rotationlock.RotationLockModule;
 import com.android.systemui.scene.SceneContainerFrameworkModule;
 import com.android.systemui.screenshot.ReferenceScreenshotModule;
-import com.android.systemui.settings.dagger.MultiUserUtilsModule;
+import com.android.systemui.settings.MultiUserUtilsModule;
 import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
 import com.android.systemui.shade.ShadeModule;
 import com.android.systemui.statusbar.CommandQueue;
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 5ee2045..a3d6ad4 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -47,7 +47,7 @@
 import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
 import com.android.systemui.media.taptotransfer.sender.MediaTttSenderCoordinator
 import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherCoreStartable
-import com.android.systemui.settings.dagger.MultiUserUtilsModule
+import com.android.systemui.settings.MultiUserUtilsModule
 import com.android.systemui.shortcut.ShortcutKeyDispatcher
 import com.android.systemui.statusbar.ImmersiveModeConfirmation
 import com.android.systemui.statusbar.gesture.GesturePointerEventListener
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt
index 91e0547..0cab10db 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt
@@ -47,24 +47,30 @@
 @Inject
 constructor(
     private val selectedComponentRepository: SelectedComponentRepository,
-    private val controlsComponent: ControlsComponent,
-    private val authorizedPanelsRepository: AuthorizedPanelsRepository,
+    controlsComponent: ControlsComponent,
+    authorizedPanelsRepository: AuthorizedPanelsRepository,
     userRepository: UserRepository,
     @Background private val bgScope: CoroutineScope
 ) {
-    private val controlsListingController =
+    private val controlsListingController: ControlsListingController? =
         controlsComponent.getControlsListingController().getOrNull()
 
     /** Gets the current user's selected panel, or null if there isn't one */
-    private val selectedItem: Flow<SelectedComponentRepository.SelectedComponent?> =
+    private val selectedPanel: Flow<SelectedComponentRepository.SelectedComponent?> =
         userRepository.selectedUserInfo
             .flatMapLatest { user ->
                 selectedComponentRepository.selectedComponentFlow(user.userHandle)
             }
             .map { if (it?.isPanel == true) it else null }
 
-    /** Gets all the available panels which are authorized by the user */
-    private fun allPanelItem(): Flow<List<PanelComponent>> {
+    /** Gets the current user's authorized panels */
+    private val allAuthorizedPanels: Flow<Set<String>> =
+        userRepository.selectedUserInfo.flatMapLatest { user ->
+            authorizedPanelsRepository.observeAuthorizedPanels(user.userHandle)
+        }
+
+    /** Gets all the available services from [ControlsListingController] */
+    private fun allAvailableServices(): Flow<List<ControlsServiceInfo>> {
         if (controlsListingController == null) {
             return emptyFlow()
         }
@@ -79,26 +85,38 @@
                 awaitClose { controlsListingController.removeCallback(listener) }
             }
             .onStart { emit(controlsListingController.getCurrentServices()) }
-            .map { serviceInfos ->
-                val authorizedPanels = authorizedPanelsRepository.getAuthorizedPanels()
-                serviceInfos.mapNotNull {
-                    val panelActivity = it.panelActivity
-                    if (it.componentName.packageName in authorizedPanels && panelActivity != null) {
-                        PanelComponent(it.componentName, panelActivity)
-                    } else {
-                        null
-                    }
+    }
+
+    /** Gets all panels which are available and authorized by the user */
+    private val allAvailableAndAuthorizedPanels: Flow<List<PanelComponent>> =
+        combine(
+            allAvailableServices(),
+            allAuthorizedPanels,
+        ) { serviceInfos, authorizedPanels ->
+            serviceInfos.mapNotNull {
+                val panelActivity = it.panelActivity
+                if (it.componentName.packageName in authorizedPanels && panelActivity != null) {
+                    PanelComponent(it.componentName, panelActivity)
+                } else {
+                    null
                 }
             }
-    }
+        }
+
     val panelComponent: StateFlow<ComponentName?> =
-        combine(allPanelItem(), selectedItem) { items, selected ->
+        combine(
+                allAvailableAndAuthorizedPanels,
+                selectedPanel,
+            ) { panels, selected ->
                 val item =
-                    items.firstOrNull { it.componentName == selected?.componentName }
-                        ?: items.firstOrNull()
+                    panels.firstOrNull { it.componentName == selected?.componentName }
+                        ?: panels.firstOrNull()
                 item?.panelActivity
             }
             .stateIn(bgScope, SharingStarted.WhileSubscribed(), null)
 
-    data class PanelComponent(val componentName: ComponentName, val panelActivity: ComponentName)
+    private data class PanelComponent(
+        val componentName: ComponentName,
+        val panelActivity: ComponentName,
+    )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index df0566e..41ce3fd 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -23,9 +23,12 @@
 import com.android.server.notification.Flags.politeNotifications
 import com.android.server.notification.Flags.vibrateWhileUnlocked
 import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
+import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
 import com.android.systemui.Flags.keyguardBottomAreaRefactor
+import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW
+import com.android.systemui.keyguard.shared.ComposeLockscreen
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
@@ -55,6 +58,11 @@
         // SceneContainer dependencies
         SceneContainerFlag.getFlagDependencies().forEach { (alpha, beta) -> alpha dependsOn beta }
         SceneContainerFlag.getMainStaticFlag() dependsOn MIGRATE_KEYGUARD_STATUS_BAR_VIEW
+
+        // ComposeLockscreen dependencies
+        ComposeLockscreen.token dependsOn KeyguardShadeMigrationNssl.token
+        ComposeLockscreen.token dependsOn keyguardBottomAreaRefactor
+        ComposeLockscreen.token dependsOn migrateClocksToBlueprint
     }
 
     private inline val politeNotifications
@@ -65,4 +73,6 @@
         get() = FlagToken(FLAG_VIBRATE_WHILE_UNLOCKED, vibrateWhileUnlocked())
     private inline val keyguardBottomAreaRefactor
         get() = FlagToken(FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor())
+    private inline val migrateClocksToBlueprint
+        get() = FlagToken(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, migrateClocksToBlueprint())
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt
index d5f082a..72a81cb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt
@@ -19,10 +19,10 @@
 @JvmInline
 value class Locked(val locked: Boolean)
 
-enum class ModifierKey(val text: String) {
+enum class ModifierKey(val displayedText: String) {
     ALT("ALT LEFT"),
     ALT_GR("ALT RIGHT"),
     CTRL("CTRL"),
-    META("META"),
+    META("ACTION"),
     SHIFT("SHIFT"),
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index b5f9c69..4cabd70 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -17,7 +17,6 @@
 package com.android.systemui.keyguard;
 
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
@@ -61,7 +60,6 @@
 import android.view.RemoteAnimationDefinition;
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
-import android.view.WindowManager;
 import android.view.WindowManagerPolicyConstants;
 import android.window.IRemoteTransition;
 import android.window.IRemoteTransitionFinishedCallback;
@@ -108,20 +106,7 @@
     private final ScreenOnCoordinator mScreenOnCoordinator;
     private final ShellTransitions mShellTransitions;
     private final DisplayTracker mDisplayTracker;
-    private PowerInteractor mPowerInteractor;
-
-    private static int newModeToLegacyMode(int newMode) {
-        switch (newMode) {
-            case WindowManager.TRANSIT_OPEN:
-            case WindowManager.TRANSIT_TO_FRONT:
-                return MODE_OPENING;
-            case WindowManager.TRANSIT_CLOSE:
-            case WindowManager.TRANSIT_TO_BACK:
-                return MODE_CLOSING;
-            default:
-                return 2; // MODE_CHANGING
-        }
-    }
+    private final PowerInteractor mPowerInteractor;
 
     private static RemoteAnimationTarget[] wrap(TransitionInfo info, boolean wallpapers,
             SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap,
@@ -253,8 +238,7 @@
 
             public void mergeAnimation(IBinder candidateTransition, TransitionInfo candidateInfo,
                     SurfaceControl.Transaction candidateT, IBinder currentTransition,
-                    IRemoteTransitionFinishedCallback candidateFinishCallback)
-                    throws RemoteException {
+                    IRemoteTransitionFinishedCallback candidateFinishCallback) {
                 if ((candidateInfo.getFlags() & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0) {
                     keyguardViewMediator.setPendingLock(true);
                     keyguardViewMediator.cancelKeyguardExitAnimation();
@@ -265,13 +249,13 @@
                     runner.onAnimationCancelled();
                     finish(currentTransition);
                 } catch (RemoteException e) {
-                    // nothing, we'll just let it finish on its own I guess.
+                    // Ignore.
                 }
             }
 
             @Override
-            public void onTransitionConsumed(IBinder transition, boolean aborted)
-                    throws RemoteException {
+            public void onTransitionConsumed(IBinder transition, boolean aborted) {
+                // No-op.
             }
 
             private static void initAlphaForAnimationTargets(@NonNull SurfaceControl.Transaction t,
@@ -283,7 +267,7 @@
             }
 
             private void finish(IBinder transition) throws RemoteException {
-                IRemoteTransitionFinishedCallback finishCallback = null;
+                final IRemoteTransitionFinishedCallback finishCallback;
                 SurfaceControl.Transaction finishTransaction = null;
 
                 synchronized (mLeashMap) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 794befa..f085e88 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -45,12 +45,12 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
 import android.app.AlarmManager;
 import android.app.BroadcastOptions;
 import android.app.IActivityTaskManager;
 import android.app.PendingIntent;
 import android.app.StatusBarManager;
-import android.app.WallpaperManager;
 import android.app.WindowConfiguration;
 import android.app.trust.TrustManager;
 import android.content.BroadcastReceiver;
@@ -174,6 +174,8 @@
 import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
 import com.android.wm.shell.keyguard.KeyguardTransitions;
 
+import dagger.Lazy;
+
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -183,7 +185,6 @@
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
-import dagger.Lazy;
 import kotlinx.coroutines.CoroutineDispatcher;
 
 /**
@@ -326,7 +327,6 @@
     private AlarmManager mAlarmManager;
     private AudioManager mAudioManager;
     private StatusBarManager mStatusBarManager;
-    private WallpaperManager mWallpaperManager;
     private final IStatusBarService mStatusBarService;
     private final IBinder mStatusBarDisableToken = new Binder();
     private final UserTracker mUserTracker;
@@ -356,13 +356,13 @@
     private final SecureSettings mSecureSettings;
     private final SystemSettings mSystemSettings;
     private final SystemClock mSystemClock;
-    private SystemPropertiesHelper mSystemPropertiesHelper;
+    private final SystemPropertiesHelper mSystemPropertiesHelper;
 
     /**
      * Used to keep the device awake while to ensure the keyguard finishes opening before
      * we sleep.
      */
-    private PowerManager.WakeLock mShowKeyguardWakeLock;
+    private final PowerManager.WakeLock mShowKeyguardWakeLock;
 
     private final Lazy<KeyguardViewController> mKeyguardViewControllerLazy;
 
@@ -405,13 +405,13 @@
     private boolean mWakeAndUnlocking = false;
 
     /**
-     * Helps remember whether the screen has turned on since the last time
-     * it turned off due to timeout. see {@link #onScreenTurnedOff(int)}
+     * Helps remember whether the screen has turned on since the last time it turned off due to
+     * timeout. See {@link #onScreenTurnedOff}
      */
     private int mDelayedShowingSequence;
 
     /**
-     * Similar to {@link #mDelayedProfileShowingSequence}, but it is for profile case.
+     * Similar to {@link #mDelayedShowingSequence}, but it is for profile case.
      */
     private int mDelayedProfileShowingSequence;
 
@@ -439,7 +439,7 @@
     private boolean mGoingToSleep;
 
     // last known state of the cellular connection
-    private String mPhoneState = TelephonyManager.EXTRA_STATE_IDLE;
+    private final String mPhoneState = TelephonyManager.EXTRA_STATE_IDLE;
 
     /**
      * Whether a hide is pending and we are just waiting for #startKeyguardExitAnimation to be
@@ -1088,8 +1088,7 @@
                 public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
                         RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
                         IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
-                    if (!handleOnAnimationStart(
-                                transit, apps, wallpapers, nonApps, finishedCallback)) {
+                    if (!handleOnAnimationStart(apps, finishedCallback)) {
                         // Usually we rely on animation completion to synchronize occluded status,
                         // but there was no animation to play, so just update it now.
                         setOccluded(true /* isOccluded */, false /* animate */);
@@ -1097,9 +1096,8 @@
                     }
                 }
 
-                private boolean handleOnAnimationStart(int transit, RemoteAnimationTarget[] apps,
-                        RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
-                        IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
+                private boolean handleOnAnimationStart(RemoteAnimationTarget[] apps,
+                        IRemoteAnimationFinishedCallback finishedCallback) {
                     if (apps == null || apps.length == 0 || apps[0] == null) {
                         Log.d(TAG, "No apps provided to the OccludeByDream runner; "
                                 + "skipping occluding animation.");
@@ -1107,8 +1105,8 @@
                     }
 
                     final RemoteAnimationTarget primary = apps[0];
-                    final boolean isDream = (apps[0].taskInfo != null
-                            && apps[0].taskInfo.topActivityType
+                    final boolean isDream = (primary.taskInfo != null
+                            && primary.taskInfo.topActivityType
                             == WindowConfiguration.ACTIVITY_TYPE_DREAM);
                     if (!isDream) {
                         Log.w(TAG, "The occluding app isn't Dream; "
@@ -1322,9 +1320,9 @@
     }
 
     private DeviceConfigProxy mDeviceConfig;
-    private DozeParameters mDozeParameters;
-    private SelectedUserInteractor mSelectedUserInteractor;
-    private KeyguardInteractor mKeyguardInteractor;
+    private final DozeParameters mDozeParameters;
+    private final SelectedUserInteractor mSelectedUserInteractor;
+    private final KeyguardInteractor mKeyguardInteractor;
     @VisibleForTesting
     protected FoldGracePeriodProvider mFoldGracePeriodProvider =
             new FoldGracePeriodProvider();
@@ -1346,14 +1344,12 @@
     private final Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
     private final InteractionJankMonitor mInteractionJankMonitor;
     private boolean mWallpaperSupportsAmbientMode;
-    private ScreenOnCoordinator mScreenOnCoordinator;
     private final KeyguardTransitions mKeyguardTransitions;
 
-    private Lazy<ActivityLaunchAnimator> mActivityLaunchAnimator;
-    private Lazy<ScrimController> mScrimControllerLazy;
-    private IActivityTaskManager mActivityTaskManagerService;
+    private final Lazy<ActivityLaunchAnimator> mActivityLaunchAnimator;
+    private final Lazy<ScrimController> mScrimControllerLazy;
+    private final IActivityTaskManager mActivityTaskManagerService;
 
-    private FeatureFlags mFeatureFlags;
     private final UiEventLogger mUiEventLogger;
     private final SessionTracker mSessionTracker;
     private final CoroutineDispatcher mMainDispatcher;
@@ -1361,7 +1357,7 @@
             mDreamingToLockscreenTransitionViewModel;
     private RemoteAnimationTarget mRemoteAnimationTarget;
 
-    private Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
+    private final Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
 
     /**
      * Injected constructor. See {@link KeyguardModule}.
@@ -1433,7 +1429,6 @@
         mShadeController = shadeControllerLazy;
         dumpManager.registerDumpable(this);
         mDeviceConfig = deviceConfig;
-        mScreenOnCoordinator = screenOnCoordinator;
         mKeyguardTransitions = keyguardTransitions;
         mNotificationShadeWindowControllerLazy = notificationShadeWindowControllerLazy;
         mShowHomeOverLockscreen = mDeviceConfig.getBoolean(
@@ -1445,9 +1440,8 @@
                 mHandler::post,
                 mOnPropertiesChangedListener);
         mInGestureNavigationMode =
-                QuickStepContract.isGesturalMode(navigationModeController.addListener(mode -> {
-                    mInGestureNavigationMode = QuickStepContract.isGesturalMode(mode);
-                }));
+                QuickStepContract.isGesturalMode(navigationModeController.addListener(mode ->
+                        mInGestureNavigationMode = QuickStepContract.isGesturalMode(mode)));
         mDozeParameters = dozeParameters;
         mSelectedUserInteractor = selectedUserInteractor;
         mKeyguardInteractor = keyguardInteractor;
@@ -1474,7 +1468,6 @@
 
         mDreamOpenAnimationDuration = (int) DREAMING_ANIMATION_DURATION_MS;
 
-        mFeatureFlags = featureFlags;
         mUiEventLogger = uiEventLogger;
         mSessionTracker = sessionTracker;
 
@@ -1578,14 +1571,6 @@
                 this::setWallpaperSupportsAmbientMode);
     }
 
-    // TODO(b/273443374) remove, temporary util to get a feature flag
-    private WallpaperManager getWallpaperManager() {
-        if (mWallpaperManager == null) {
-            mWallpaperManager = mContext.getSystemService(WallpaperManager.class);
-        }
-        return mWallpaperManager;
-    }
-
     @Override
     public void start() {
         synchronized (this) {
@@ -1611,11 +1596,11 @@
 
             ViewRootImpl viewRootImpl = mKeyguardViewControllerLazy.get().getViewRootImpl();
             if (viewRootImpl != null) {
-                collectFlow(viewRootImpl.getView(),
-                        mDreamingToLockscreenTransitionViewModel.get().getDreamOverlayAlpha(),
+                DreamingToLockscreenTransitionViewModel viewModel =
+                        mDreamingToLockscreenTransitionViewModel.get();
+                collectFlow(viewRootImpl.getView(), viewModel.getDreamOverlayAlpha(),
                         getRemoteSurfaceAlphaApplier(), mMainDispatcher);
-                collectFlow(viewRootImpl.getView(),
-                        mDreamingToLockscreenTransitionViewModel.get().getTransitionEnded(),
+                collectFlow(viewRootImpl.getView(), viewModel.getTransitionEnded(),
                         getFinishedCallbackConsumer(), mMainDispatcher);
             }
         }
@@ -2304,6 +2289,7 @@
         showKeyguard(options);
     }
 
+    @SuppressLint("MissingPermission")
     private void lockProfile(int userId) {
         mTrustManager.setDeviceLockedForUser(userId, true);
     }
@@ -2497,13 +2483,12 @@
     };
 
     /**
-     * This handler will be associated with the policy thread, which will also
-     * be the UI thread of the keyguard.  Since the apis of the policy, and therefore
-     * this class, can be called by other threads, any action that directly
-     * interacts with the keyguard ui should be posted to this handler, rather
-     * than called directly.
+     * This handler will be associated with the policy thread, which will also be the UI thread of
+     * the keyguard.  Since the apis of the policy, and therefore this class, can be called by other
+     * threads, any action that directly interacts with the keyguard ui should be posted to this
+     * handler, rather than called directly.
      */
-    private Handler mHandler = new Handler(Looper.myLooper(), null, true /*async*/) {
+    private final Handler mHandler = new Handler(Looper.myLooper(), null, true /*async*/) {
         @Override
         public void handleMessage(Message msg) {
             String message = "";
@@ -2766,7 +2751,7 @@
 
             try {
                 mActivityTaskManagerService.setLockScreenShown(showing, aodShowing);
-            } catch (RemoteException e) {
+            } catch (RemoteException ignored) {
             }
         });
     }
@@ -2790,9 +2775,8 @@
             if (!mSystemReady) {
                 if (DEBUG) Log.d(TAG, "ignoring handleShow because system is not ready.");
                 return;
-            } else {
-                if (DEBUG) Log.d(TAG, "handleShow");
             }
+            if (DEBUG) Log.d(TAG, "handleShow");
 
             mKeyguardExitAnimationRunner = null;
             mWakeAndUnlocking = false;
@@ -2851,6 +2835,7 @@
     }
 
     private final Runnable mKeyguardGoingAwayRunnable = new Runnable() {
+        @SuppressLint("MissingPermission")
         @Override
         public void run() {
             Trace.beginSection("KeyguardViewMediator.mKeyGuardGoingAwayRunnable");
@@ -2925,24 +2910,13 @@
             return;
         }
 
-        final String reasonDescription;
-
-        switch(reason) {
-            case WakeAndUnlockUpdateReason.FULFILL:
-                reasonDescription = "fulfilling existing request";
-                break;
-            case WakeAndUnlockUpdateReason.HIDE:
-                reasonDescription = "hiding keyguard";
-                break;
-            case WakeAndUnlockUpdateReason.SHOW:
-                reasonDescription = "showing keyguard";
-                break;
-            case WakeAndUnlockUpdateReason.WAKE_AND_UNLOCK:
-                reasonDescription = "waking to unlock";
-                break;
-            default:
-                throw new IllegalStateException("Unexpected value: " + reason);
-        }
+        final String reasonDescription = switch (reason) {
+            case WakeAndUnlockUpdateReason.FULFILL -> "fulfilling existing request";
+            case WakeAndUnlockUpdateReason.HIDE -> "hiding keyguard";
+            case WakeAndUnlockUpdateReason.SHOW -> "showing keyguard";
+            case WakeAndUnlockUpdateReason.WAKE_AND_UNLOCK -> "waking to unlock";
+            default -> throw new IllegalStateException("Unexpected value: " + reason);
+        };
 
         final boolean unsetUnfulfilled = !updatedValue
                 && reason != WakeAndUnlockUpdateReason.FULFILL;
@@ -3057,7 +3031,7 @@
                 IRemoteAnimationFinishedCallback callback =
                         new IRemoteAnimationFinishedCallback() {
                             @Override
-                            public void onAnimationFinished() throws RemoteException {
+                            public void onAnimationFinished() {
                                 if (!KeyguardWmStateRefactor.isEnabled()) {
                                     try {
                                         finishedCallback.onAnimationFinished();
@@ -3542,11 +3516,6 @@
     /**
      * Registers the CentralSurfaces to which the Keyguard View is mounted.
      *
-     * @param centralSurfaces
-     * @param panelView
-     * @param biometricUnlockController
-     * @param notificationContainer
-     * @param bypassController
      * @return the View Controller for the Keyguard View this class is mediating.
      */
     public KeyguardViewController registerCentralSurfaces(CentralSurfaces centralSurfaces,
@@ -3773,9 +3742,7 @@
             }
         });
         updateInputRestrictedLocked();
-        mUiBgExecutor.execute(() -> {
-            mTrustManager.reportKeyguardShowingChanged();
-        });
+        mUiBgExecutor.execute(mTrustManager::reportKeyguardShowingChanged);
     }
 
     private void notifyTrustedChangedLocked(boolean trusted) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index e16f8dc..70da3e7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -90,10 +90,12 @@
 import java.util.concurrent.Executor;
 
 import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
 
 /**
  * Dagger Module providing keyguard.
  */
+@ExperimentalCoroutinesApi
 @Module(subcomponents = {
         KeyguardQsUserSwitchComponent.class,
         KeyguardStatusBarViewComponent.class,
@@ -115,7 +117,7 @@
      */
     @Provides
     @SysUISingleton
-    public static KeyguardViewMediator newKeyguardViewMediator(
+    static KeyguardViewMediator newKeyguardViewMediator(
             Context context,
             UiEventLogger uiEventLogger,
             SessionTracker sessionTracker,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
index 9a13558d..b152eea 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
@@ -22,6 +22,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.biometrics.AuthController
+import com.android.systemui.biometrics.shared.model.AuthenticationReason
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
@@ -174,6 +175,8 @@
                     mainDispatcher
                 ) // keyguardUpdateMonitor requires registration on main thread.
 
+    // TODO(b/322555228) Remove after consolidating device entry auth messages with BP auth messages
+    //  in BiometricStatusRepository
     override val authenticationStatus: Flow<FingerprintAuthenticationStatus>
         get() = conflatedCallbackFlow {
             val callback =
@@ -236,7 +239,8 @@
                         sendUpdateIfFingerprint(
                             biometricSourceType,
                             AcquiredFingerprintAuthenticationStatus(
-                                acquireInfo,
+                                AuthenticationReason.DeviceEntryAuthentication,
+                                acquireInfo
                             ),
                         )
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
index b1a2297..e017129 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
@@ -94,6 +94,7 @@
             context.resources.getBoolean(R.bool.config_show_sidefps_hint_on_bouncer)
         val sfpsDetectionRunning = keyguardUpdateMonitor.isFingerprintDetectionRunning
         val isUnlockingWithFpAllowed = keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed
+
         return primaryBouncerInteractor.isBouncerShowing() &&
             sfpsEnabled &&
             sfpsDetectionRunning &&
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt
new file mode 100644
index 0000000..7f0b483
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 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
+
+import com.android.systemui.Flags
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the compose lockscreen flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object ComposeLockscreen {
+    /** The aconfig flag name */
+    const val FLAG_NAME = Flags.FLAG_COMPOSE_LOCKSCREEN
+
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
+    /** Is the refactor enabled */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.composeLockscreen() && ComposeFacade.isComposeAvailable()
+
+    /**
+     * Called to ensure code is only run when the flag is enabled. This protects users from the
+     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+     * build to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun isUnexpectedlyInLegacyMode() =
+        RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is enabled to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
index cc385a8..474de77 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
@@ -20,6 +20,7 @@
 import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
 import android.hardware.fingerprint.FingerprintManager
 import android.os.SystemClock.elapsedRealtime
+import com.android.systemui.biometrics.shared.model.AuthenticationReason
 
 /**
  * Fingerprint authentication status provided by
@@ -40,8 +41,10 @@
 ) : FingerprintAuthenticationStatus()
 
 /** Fingerprint acquired message. */
-data class AcquiredFingerprintAuthenticationStatus(val acquiredInfo: Int) :
-    FingerprintAuthenticationStatus() {
+data class AcquiredFingerprintAuthenticationStatus(
+    val authenticationReason: AuthenticationReason,
+    val acquiredInfo: Int
+) : FingerprintAuthenticationStatus() {
 
     val fingerprintCaptureStarted: Boolean = acquiredInfo == FINGERPRINT_ACQUIRED_START
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 48092c6..789d30f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -191,6 +191,7 @@
                                 .collect { y ->
                                     childViews[burnInLayerId]?.translationY = y
                                     childViews[largeClockId]?.translationY = y
+                                    childViews[aodNotificationIconContainerId]?.translationY = y
                                 }
                         }
 
@@ -200,6 +201,7 @@
                                 .collect { x ->
                                     childViews[burnInLayerId]?.translationX = x
                                     childViews[largeClockId]?.translationX = x
+                                    childViews[aodNotificationIconContainerId]?.translationX = x
                                 }
                         }
 
@@ -219,6 +221,10 @@
                                         // transition with other parts in burnInLayer
                                         childViews[burnInLayerId]?.scaleX = scaleViewModel.scale
                                         childViews[burnInLayerId]?.scaleY = scaleViewModel.scale
+                                        childViews[aodNotificationIconContainerId]?.scaleX =
+                                            scaleViewModel.scale
+                                        childViews[aodNotificationIconContainerId]?.scaleY =
+                                            scaleViewModel.scale
                                     }
                                 }
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
index 3d36eb0..9a1fcc1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
@@ -41,11 +41,13 @@
             return
         }
 
-        val nic = constraintLayout.requireViewById<View>(R.id.aod_notification_icon_container)
+        // The burn-in layer requires at least 1 view at all times
+        val emptyView = View(context, null).apply { id = View.generateViewId() }
+        constraintLayout.addView(emptyView)
         burnInLayer =
             AodBurnInLayer(context).apply {
                 id = R.id.burn_in_layer
-                addView(nic)
+                addView(emptyView)
                 if (!migrateClocksToBlueprint()) {
                     val statusView =
                         constraintLayout.requireViewById<View>(R.id.keyguard_status_view)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
index ca9c857..67c42f0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
@@ -22,8 +22,10 @@
 import androidx.annotation.VisibleForTesting
 import androidx.core.animation.addListener
 import com.android.systemui.Flags
+import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
 import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor
+import com.android.systemui.biometrics.shared.model.AuthenticationReason
 import com.android.systemui.biometrics.shared.model.DisplayRotation
 import com.android.systemui.biometrics.shared.model.isDefaultOrientation
 import com.android.systemui.dagger.SysUISingleton
@@ -34,6 +36,7 @@
 import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.res.R
@@ -49,10 +52,12 @@
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onCompletion
 import kotlinx.coroutines.launch
 
@@ -62,7 +67,8 @@
 @Inject
 constructor(
     private val context: Context,
-    private val fpAuthRepository: DeviceEntryFingerprintAuthInteractor,
+    private val biometricStatusInteractor: BiometricStatusInteractor,
+    private val deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
     private val sfpsSensorInteractor: SideFpsSensorInteractor,
     // todo (b/317432075) Injecting DozeServiceHost directly instead of using it through
     //  DozeInteractor as DozeServiceHost already depends on DozeInteractor.
@@ -86,6 +92,23 @@
     private val additionalSensorLengthPadding =
         context.resources.getDimension(R.dimen.sfps_progress_bar_length_extra_padding).toInt()
 
+    // Merged [FingerprintAuthenticationStatus] from BiometricPrompt acquired messages and
+    // device entry authentication messages
+    private val mergedFingerprintAuthenticationStatus =
+        merge(
+                biometricStatusInteractor.fingerprintAcquiredStatus,
+                deviceEntryFingerprintAuthInteractor.authenticationStatus
+            )
+            .filter {
+                if (it is AcquiredFingerprintAuthenticationStatus) {
+                    it.authenticationReason == AuthenticationReason.DeviceEntryAuthentication ||
+                        it.authenticationReason ==
+                            AuthenticationReason.BiometricPromptAuthentication
+                } else {
+                    true
+                }
+            }
+
     val isVisible: Flow<Boolean> = _visible.asStateFlow()
 
     val progress: Flow<Float> = _progress.asStateFlow()
@@ -147,7 +170,14 @@
                 viewLeftTop
             }
 
-    val isFingerprintAuthRunning: Flow<Boolean> = fpAuthRepository.isRunning
+    val isFingerprintAuthRunning: Flow<Boolean> =
+        combine(
+            deviceEntryFingerprintAuthInteractor.isRunning,
+            biometricStatusInteractor.sfpsAuthenticationReason
+        ) { deviceEntryAuthIsRunning, sfpsAuthReason ->
+            deviceEntryAuthIsRunning ||
+                sfpsAuthReason == AuthenticationReason.BiometricPromptAuthentication
+        }
 
     val rotation: Flow<Float> =
         combine(displayStateInteractor.currentRotation, sfpsSensorInteractor.sensorLocation, ::Pair)
@@ -185,7 +215,8 @@
                     sfpsSensorInteractor.authenticationDuration
                         .flatMapLatest { authDuration ->
                             _animator?.cancel()
-                            fpAuthRepository.authenticationStatus.map { authStatus ->
+                            mergedFingerprintAuthenticationStatus.map {
+                                authStatus: FingerprintAuthenticationStatus ->
                                 when (authStatus) {
                                     is AcquiredFingerprintAuthenticationStatus -> {
                                         if (authStatus.fingerprintCaptureStarted) {
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 23029e6..ac579d6 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -628,4 +628,13 @@
     public static LogBuffer providePackageChangeRepoLogBuffer(LogBufferFactory factory) {
         return factory.create("PackageChangeRepo", 50);
     }
+
+    /** Provides a {@link LogBuffer} for NavBarButtonClicks. */
+    @Provides
+    @SysUISingleton
+    @NavBarButtonClickLog
+    public static LogBuffer provideNavBarButtonClickLogBuffer(LogBufferFactory factory) {
+        return factory.create("NavBarButtonClick", 50);
+    }
+
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NavBarButtonClickLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NavBarButtonClickLog.java
new file mode 100644
index 0000000..939dab2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NavBarButtonClickLog.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 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 static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/** A {@link LogBuffer} for {@link com.android.systemui.navigationbar.NavigationBar}. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface NavBarButtonClickLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarButtonClickLogger.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarButtonClickLogger.kt
new file mode 100644
index 0000000..408acf3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarButtonClickLogger.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 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.navigationbar
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.NavBarButtonClickLog
+import javax.inject.Inject
+
+class NavBarButtonClickLogger
+@Inject
+constructor(@NavBarButtonClickLog private val buffer: LogBuffer) {
+    fun logHomeButtonClick() {
+        buffer.log(TAG, LogLevel.DEBUG, {}, { "Home Button Triggered" })
+    }
+
+    fun logBackButtonClick() {
+        buffer.log(TAG, LogLevel.DEBUG, {}, { "Back Button Triggered" })
+    }
+
+    fun logRecentsButtonClick() {
+        buffer.log(TAG, LogLevel.DEBUG, {}, { "Recents Button Triggered" })
+    }
+
+    fun logImeSwitcherClick() {
+        buffer.log(TAG, LogLevel.DEBUG, {}, { "Ime Switcher Triggered" })
+    }
+
+    fun logAccessibilityButtonClick() {
+        buffer.log(TAG, LogLevel.DEBUG, {}, { "Accessibility Button Triggered" })
+    }
+}
+
+private const val TAG = "NavBarButtonClick"
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 068e5fd..95b75ac 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -84,11 +84,7 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.Surface;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.Transaction;
 import android.view.View;
-import android.view.ViewRootImpl;
-import android.view.ViewRootImpl.SurfaceChangedCallback;
 import android.view.ViewTreeObserver;
 import android.view.ViewTreeObserver.InternalInsetsInfo;
 import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
@@ -285,6 +281,7 @@
     private boolean mImeVisible;
     private final Rect mSamplingBounds = new Rect();
     private final Binder mInsetsSourceOwner = new Binder();
+    private final NavBarButtonClickLogger mNavBarButtonClickLogger;
 
     /**
      * When quickswitching between apps of different orientations, we draw a secondary home handle
@@ -559,7 +556,8 @@
             UserContextProvider userContextProvider,
             WakefulnessLifecycle wakefulnessLifecycle,
             TaskStackChangeListeners taskStackChangeListeners,
-            DisplayTracker displayTracker) {
+            DisplayTracker displayTracker,
+            NavBarButtonClickLogger navBarButtonClickLogger) {
         super(navigationBarView);
         mFrame = navigationBarFrame;
         mContext = context;
@@ -601,6 +599,7 @@
         mTaskStackChangeListeners = taskStackChangeListeners;
         mDisplayTracker = displayTracker;
         mEdgeBackGestureHandler = navBarHelper.getEdgeBackGestureHandler();
+        mNavBarButtonClickLogger = navBarButtonClickLogger;
 
         mNavColorSampleMargin = getResources()
                 .getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin);
@@ -1276,6 +1275,10 @@
 
         ButtonDispatcher homeButton = mView.getHomeButton();
         homeButton.setOnTouchListener(this::onHomeTouch);
+        homeButton.setNavBarButtonClickLogger(mNavBarButtonClickLogger);
+
+        ButtonDispatcher backButton = mView.getBackButton();
+        backButton.setNavBarButtonClickLogger(mNavBarButtonClickLogger);
 
         reconfigureHomeLongClick();
 
@@ -1388,6 +1391,8 @@
     }
 
     private void onRecentsClick(View v) {
+        mNavBarButtonClickLogger.logRecentsButtonClick();
+
         if (LatencyTracker.isEnabled(mContext)) {
             LatencyTracker.getInstance(mContext).onActionStart(
                     LatencyTracker.ACTION_TOGGLE_RECENTS);
@@ -1397,6 +1402,7 @@
     }
 
     private void onImeSwitcherClick(View v) {
+        mNavBarButtonClickLogger.logImeSwitcherClick();
         mInputMethodManager.showInputMethodPickerFromSystem(
                 true /* showAuxiliarySubtypes */, mDisplayId);
         mUiEventLogger.log(KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_TAP);
@@ -1486,6 +1492,7 @@
     }
 
     private void onAccessibilityClick(View v) {
+        mNavBarButtonClickLogger.logAccessibilityButtonClick();
         final Display display = v.getDisplay();
         mAccessibilityManager.notifyAccessibilityButtonClicked(
                 display != null ? display.getDisplayId() : mDisplayTracker.getDefaultDisplayId());
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
index 5739abc..fc37b9f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
@@ -23,6 +23,9 @@
 import android.animation.ValueAnimator;
 import android.view.View;
 import android.view.View.AccessibilityDelegate;
+import android.view.ViewGroup;
+
+import com.android.systemui.navigationbar.NavBarButtonClickLogger;
 
 import java.util.ArrayList;
 
@@ -52,6 +55,7 @@
     private boolean mVertical;
     private ValueAnimator mFadeAnimator;
     private AccessibilityDelegate mAccessibilityDelegate;
+    private NavBarButtonClickLogger mNavBarButtonClickLogger;
 
     private final ValueAnimator.AnimatorUpdateListener mAlphaListener = animation ->
             setAlpha(
@@ -341,4 +345,36 @@
      */
     public void onDestroy() {
     }
+
+    /**
+     * Sets the NavBarButtonClickLogger for all the KeyButtonViews respectively.
+     */
+    public void setNavBarButtonClickLogger(NavBarButtonClickLogger navBarButtonClickLogger) {
+        if (navBarButtonClickLogger != null) {
+            mNavBarButtonClickLogger = navBarButtonClickLogger;
+            final int size = mViews.size();
+            for (int i = 0; i < size; i++) {
+                final View v = mViews.get(i);
+                setNavBarButtonClickLoggerForViewChildren(v);
+            }
+        }
+    }
+
+    /**
+     * Recursively explores view hierarchy until the children of provided view are of type
+     * KeyButtonView, so the NavBarButtonClickLogger can be set on them.
+     */
+    private void setNavBarButtonClickLoggerForViewChildren(View v) {
+        if (v instanceof KeyButtonView) {
+            ((KeyButtonView) v).setNavBarButtonClickLogger(mNavBarButtonClickLogger);
+            return;
+        }
+
+        if (v instanceof ViewGroup viewGroup) {
+            final int childrenCount = viewGroup.getChildCount();
+            for (int i = 0; i < childrenCount; i++) {
+                setNavBarButtonClickLoggerForViewChildren(viewGroup.getChildAt(i));
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
index df6843d..dbe87ea 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
@@ -59,6 +59,7 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.Dependency;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.navigationbar.NavBarButtonClickLogger;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.res.R;
 import com.android.systemui.shared.navigationbar.KeyButtonRipple;
@@ -86,6 +87,7 @@
     private final Paint mOvalBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
     private float mDarkIntensity;
     private boolean mHasOvalBg = false;
+    private NavBarButtonClickLogger mNavBarButtonClickLogger;
 
     @VisibleForTesting
     public enum NavBarButtonEvent implements UiEventLogger.UiEventEnum {
@@ -197,6 +199,10 @@
         mOnClickListener = onClickListener;
     }
 
+    public void setNavBarButtonClickLogger(NavBarButtonClickLogger navBarButtonClickLogger) {
+        mNavBarButtonClickLogger = navBarButtonClickLogger;
+    }
+
     public void loadAsync(Icon icon) {
         new AsyncTask<Icon, Void, Drawable>() {
             @Override
@@ -389,11 +395,19 @@
                 uiEvent = longPressSet
                         ? NavBarButtonEvent.NAVBAR_BACK_BUTTON_LONGPRESS
                         : NavBarButtonEvent.NAVBAR_BACK_BUTTON_TAP;
+
+                if (mNavBarButtonClickLogger != null) {
+                    mNavBarButtonClickLogger.logBackButtonClick();
+                }
                 break;
             case KeyEvent.KEYCODE_HOME:
                 uiEvent = longPressSet
                         ? NavBarButtonEvent.NAVBAR_HOME_BUTTON_LONGPRESS
                         : NavBarButtonEvent.NAVBAR_HOME_BUTTON_TAP;
+
+                if (mNavBarButtonClickLogger != null) {
+                    mNavBarButtonClickLogger.logHomeButtonClick();
+                }
                 break;
             case KeyEvent.KEYCODE_APP_SWITCH:
                 uiEvent = longPressSet
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 37abc40..56c0ca9 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -52,6 +52,7 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.distinctUntilChangedBy
 import kotlinx.coroutines.flow.emptyFlow
@@ -115,11 +116,15 @@
     private fun hydrateVisibility() {
         applicationScope.launch {
             // TODO(b/296114544): Combine with some global hun state to make it visible!
-            deviceProvisioningInteractor.isFactoryResetProtectionActive
-                .flatMapLatest { isFrpActive ->
-                    if (isFrpActive) {
-                        flowOf(false to "Factory Reset Protection is active")
-                    } else {
+            combine(
+                    deviceProvisioningInteractor.isDeviceProvisioned,
+                    deviceProvisioningInteractor.isFactoryResetProtectionActive,
+                ) { isDeviceProvisioned, isFrpActive ->
+                    isDeviceProvisioned && !isFrpActive
+                }
+                .distinctUntilChanged()
+                .flatMapLatest { isAllowedToBeVisible ->
+                    if (isAllowedToBeVisible) {
                         sceneInteractor.transitionState
                             .mapNotNull { state ->
                                 when (state) {
@@ -140,6 +145,8 @@
                                 }
                             }
                             .distinctUntilChanged()
+                    } else {
+                        flowOf(false to "Device not provisioned or Factory Reset Protection active")
                     }
                 }
                 .collect { (isVisible, loggingReason) ->
diff --git a/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java b/packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java
similarity index 85%
rename from packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java
rename to packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java
index a0dd924..fd807db 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.settings.dagger;
+package com.android.systemui.settings;
 
 import android.app.ActivityManager;
 import android.app.IActivityManager;
@@ -29,14 +29,6 @@
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlagsClassic;
-import com.android.systemui.settings.DisplayTracker;
-import com.android.systemui.settings.DisplayTrackerImpl;
-import com.android.systemui.settings.UserContentResolverProvider;
-import com.android.systemui.settings.UserContextProvider;
-import com.android.systemui.settings.UserFileManager;
-import com.android.systemui.settings.UserFileManagerImpl;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.settings.UserTrackerImpl;
 
 import dagger.Binds;
 import dagger.Module;
diff --git a/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt
new file mode 100644
index 0000000..76d1d3d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.settings
+
+import android.content.ContentResolver
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepositoryImpl
+import dagger.Module
+import dagger.Provides
+import kotlinx.coroutines.CoroutineDispatcher
+
+@Module
+object SecureSettingsRepositoryModule {
+    @JvmStatic
+    @Provides
+    @SysUISingleton
+    fun provideSecureSettingsRepository(
+        contentResolver: ContentResolver,
+        @Background backgroundDispatcher: CoroutineDispatcher,
+    ): SecureSettingsRepository =
+        SecureSettingsRepositoryImpl(contentResolver, backgroundDispatcher)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt
deleted file mode 100644
index b09bfe2..0000000
--- a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2024 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.settings
-
-import android.annotation.UserIdInt
-import android.content.Context
-import android.content.SharedPreferences
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-
-/** Extension functions for [UserFileManager]. */
-object UserFileManagerExt {
-
-    /** Returns a flow of [Unit] that is invoked each time the shared preference is updated. */
-    fun UserFileManager.observeSharedPreferences(
-        fileName: String,
-        @Context.PreferencesMode mode: Int,
-        @UserIdInt userId: Int
-    ): Flow<Unit> = conflatedCallbackFlow {
-        val sharedPrefs = getSharedPreferences(fileName, mode, userId)
-
-        val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, _ -> trySend(Unit) }
-
-        sharedPrefs.registerOnSharedPreferenceChangeListener(listener)
-        awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index a01ac70..f7fed53 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -183,7 +183,8 @@
         mBackgroundExecutor = backgroundExecutor;
         mColorExtractor = colorExtractor;
         mScreenOffAnimationController = screenOffAnimationController;
-        dumpManager.registerDumpable(this);
+        // prefix with {slow} to make sure this dumps at the END of the critical section.
+        dumpManager.registerCriticalDumpable("{slow}NotificationShadeWindowControllerImpl", this);
         mAuthController = authController;
         mUserInteractor = userInteractor;
         mSceneContainerFlags = sceneContainerFlags;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
index 10b9db0..4e8b403 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.assist.AssistManager
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.dagger.ShadeTouchLog
@@ -34,11 +35,13 @@
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import dagger.Lazy
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 
 /**
  * Implementation of ShadeController backed by scenes instead of NPVC.
@@ -50,6 +53,7 @@
 class ShadeControllerSceneImpl
 @Inject
 constructor(
+    @Main private val mainDispatcher: CoroutineDispatcher,
     @Background private val scope: CoroutineScope,
     private val shadeInteractor: ShadeInteractor,
     private val sceneInteractor: SceneInteractor,
@@ -193,7 +197,11 @@
     }
 
     override fun setVisibilityListener(listener: ShadeVisibilityListener) {
-        scope.launch { sceneInteractor.isVisible.collect { listener.expandedVisibleChanged(it) } }
+        scope.launch {
+            sceneInteractor.isVisible.collect { isVisible ->
+                withContext(mainDispatcher) { listener.expandedVisibleChanged(isVisible) }
+            }
+        }
     }
 
     @ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 7f8be1c..ef50265 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -14,6 +14,7 @@
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.Dumpable
 import com.android.systemui.ExpandHelper
+import com.android.systemui.Flags.nsslFalsingFix
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.biometrics.UdfpsKeyguardViewControllerLegacy
 import com.android.systemui.classifier.Classifier
@@ -889,7 +890,7 @@
                     isDraggingDown = false
                     isTrackpadReverseScroll = false
                     shadeRepository.setLegacyLockscreenShadeTracking(false)
-                    if (KeyguardShadeMigrationNssl.isEnabled) {
+                    if (nsslFalsingFix() || KeyguardShadeMigrationNssl.isEnabled) {
                         return true
                     }
                 } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 32cd56c..b64e0b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -56,7 +56,6 @@
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl;
 import com.android.systemui.statusbar.phone.StatusBarIconList;
-import com.android.systemui.statusbar.phone.StatusBarNotificationPresenterModule;
 import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -73,7 +72,7 @@
  * their own version of CentralSurfaces can include just dependencies, without injecting
  * CentralSurfaces itself.
  */
-@Module(includes = {StatusBarNotificationPresenterModule.class})
+@Module
 public interface CentralSurfacesDependenciesModule {
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesModule.java
index 99d4b2e..27536bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesModule.java
@@ -18,12 +18,14 @@
 
 import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
 import com.android.systemui.statusbar.notification.row.NotificationRowModule;
+import com.android.systemui.statusbar.phone.StatusBarNotificationPresenterModule;
 import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
 
 import dagger.Module;
 
-/** */
-@Module(includes = {StatusBarPhoneModule.class, CentralSurfacesDependenciesModule.class,
+/**  */
+@Module(includes = {CentralSurfacesDependenciesModule.class,
+        StatusBarNotificationPresenterModule.class, StatusBarPhoneModule.class,
         NotificationsModule.class, NotificationRowModule.class})
 public interface CentralSurfacesModule {
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
index ae4ba27..29627e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
@@ -18,7 +18,7 @@
 
 import android.os.UserHandle
 import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.Flags.screenshareNotificationHiding
+import com.android.server.notification.Flags.screenshareNotificationHiding
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.StatusBarState
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index cd816ae..954e805 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.notification.collection.inflation;
 
-import static com.android.systemui.Flags.screenshareNotificationHiding;
+import static com.android.server.notification.Flags.screenshareNotificationHiding;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 6bba72b..92b0c04 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -51,6 +51,7 @@
 import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.data.NotificationDataLayerModule;
+import com.android.systemui.statusbar.notification.domain.NotificationDomainLayerModule;
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor;
 import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModelModule;
 import com.android.systemui.statusbar.notification.icon.ConversationIconManager;
@@ -78,14 +79,14 @@
 import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
-import javax.inject.Provider;
-
 import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
 import dagger.multibindings.ClassKey;
 import dagger.multibindings.IntoMap;
 
+import javax.inject.Provider;
+
 /**
  * Dagger Module for classes found within the com.android.systemui.statusbar.notification package.
  */
@@ -94,6 +95,7 @@
         FooterViewModelModule.class,
         KeyguardNotificationVisibilityProviderModule.class,
         NotificationDataLayerModule.class,
+        NotificationDomainLayerModule.class,
         NotifPipelineChoreographerModule.class,
         NotificationSectionHeadersModule.class,
         ActivatableNotificationViewModelModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
index 2cac000..b187cf1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
@@ -17,4 +17,10 @@
 
 import dagger.Module
 
-@Module(includes = []) interface NotificationDataLayerModule
+@Module(
+    includes =
+        [
+            NotificationSettingsRepositoryModule::class,
+        ]
+)
+interface NotificationDataLayerModule
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt
new file mode 100644
index 0000000..a7970c7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.data
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.SecureSettingsRepositoryModule
+import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
+import dagger.Module
+import dagger.Provides
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+
+@Module(includes = [SecureSettingsRepositoryModule::class])
+object NotificationSettingsRepositoryModule {
+    @Provides
+    @SysUISingleton
+    fun provideNotificationSettingsRepository(
+        @Background backgroundScope: CoroutineScope,
+        @Background backgroundDispatcher: CoroutineDispatcher,
+        secureSettingsRepository: SecureSettingsRepository,
+    ): NotificationSettingsRepository =
+        NotificationSettingsRepository(
+            backgroundScope,
+            backgroundDispatcher,
+            secureSettingsRepository
+        )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/NotificationDomainLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/NotificationDomainLayerModule.kt
new file mode 100644
index 0000000..5c49b28
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/NotificationDomainLayerModule.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.domain
+
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationSettingsInteractorModule
+import dagger.Module
+
+@Module(includes = [NotificationSettingsInteractorModule::class])
+object NotificationDomainLayerModule
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationSettingsInteractorModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationSettingsInteractorModule.kt
new file mode 100644
index 0000000..0a9e12a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationSettingsInteractorModule.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
+import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
+import dagger.Module
+import dagger.Provides
+
+@Module
+object NotificationSettingsInteractorModule {
+    @Provides
+    @SysUISingleton
+    fun provideNotificationSettingsInteractor(repository: NotificationSettingsRepository) =
+        NotificationSettingsInteractor(repository)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index 3616fd6d..16f18a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -54,7 +54,7 @@
     private static final String TAG = "FooterView";
 
     private FooterViewButton mClearAllButton;
-    private FooterViewButton mManageButton;
+    private FooterViewButton mManageOrHistoryButton;
     private boolean mShowHistory;
     // String cache, for performance reasons.
     // Reading them from a Resources object can be quite slow sometimes.
@@ -68,6 +68,8 @@
 
     private @StringRes int mClearAllButtonTextId;
     private @StringRes int mClearAllButtonDescriptionId;
+    private @StringRes int mManageOrHistoryButtonTextId;
+    private @StringRes int mManageOrHistoryButtonDescriptionId;
     private @StringRes int mMessageStringId;
     private @DrawableRes int mMessageIconId;
 
@@ -155,6 +157,43 @@
         mClearAllButton.setContentDescription(getContext().getString(mClearAllButtonDescriptionId));
     }
 
+    /** Set the text label for the "Manage"/"History" button. */
+    public void setManageOrHistoryButtonText(@StringRes int textId) {
+        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
+        if (mManageOrHistoryButtonTextId == textId) {
+            return; // nothing changed
+        }
+        mManageOrHistoryButtonTextId = textId;
+        updateManageOrHistoryButtonText();
+    }
+
+    private void updateManageOrHistoryButtonText() {
+        if (mManageOrHistoryButtonTextId == 0) {
+            return; // not initialized yet
+        }
+        mManageOrHistoryButton.setText(getContext().getString(mManageOrHistoryButtonTextId));
+    }
+
+    /** Set the accessibility content description for the "Clear all" button. */
+    public void setManageOrHistoryButtonDescription(@StringRes int contentDescriptionId) {
+        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
+            return;
+        }
+        if (mManageOrHistoryButtonDescriptionId == contentDescriptionId) {
+            return; // nothing changed
+        }
+        mManageOrHistoryButtonDescriptionId = contentDescriptionId;
+        updateManageOrHistoryButtonDescription();
+    }
+
+    private void updateManageOrHistoryButtonDescription() {
+        if (mManageOrHistoryButtonDescriptionId == 0) {
+            return; // not initialized yet
+        }
+        mManageOrHistoryButton.setContentDescription(
+                getContext().getString(mManageOrHistoryButtonDescriptionId));
+    }
+
     /** Set the string for a message to be shown instead of the buttons. */
     public void setMessageString(@StringRes int messageId) {
         if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
@@ -173,7 +212,6 @@
         mSeenNotifsFooterTextView.setText(messageString);
     }
 
-
     /** Set the icon to be shown before the message (see {@link #setMessageString(int)}). */
     public void setMessageIcon(@DrawableRes int iconId) {
         if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
@@ -203,9 +241,11 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mClearAllButton = (FooterViewButton) findSecondaryView();
-        mManageButton = findViewById(R.id.manage_text);
+        mManageOrHistoryButton = findViewById(R.id.manage_text);
         mSeenNotifsFooterTextView = findViewById(R.id.unlock_prompt_footer);
-        updateResources();
+        if (!FooterViewRefactor.isEnabled()) {
+            updateResources();
+        }
         updateContent();
         updateColors();
     }
@@ -213,11 +253,11 @@
     /** Show a message instead of the footer buttons. */
     public void setFooterLabelVisible(boolean isVisible) {
         if (isVisible) {
-            mManageButton.setVisibility(View.GONE);
+            mManageOrHistoryButton.setVisibility(View.GONE);
             mClearAllButton.setVisibility(View.GONE);
             mSeenNotifsFooterTextView.setVisibility(View.VISIBLE);
         } else {
-            mManageButton.setVisibility(View.VISIBLE);
+            mManageOrHistoryButton.setVisibility(View.VISIBLE);
             mClearAllButton.setVisibility(View.VISIBLE);
             mSeenNotifsFooterTextView.setVisibility(View.GONE);
         }
@@ -225,7 +265,7 @@
 
     /** Set onClickListener for the manage/history button. */
     public void setManageButtonClickListener(OnClickListener listener) {
-        mManageButton.setOnClickListener(listener);
+        mManageOrHistoryButton.setOnClickListener(listener);
     }
 
     /** Set onClickListener for the clear all (end) button. */
@@ -252,6 +292,7 @@
 
     /** Show "History" instead of "Manage" on the start button. */
     public void showHistory(boolean showHistory) {
+        FooterViewRefactor.assertInLegacyMode();
         if (mShowHistory == showHistory) {
             return;
         }
@@ -260,17 +301,13 @@
     }
 
     private void updateContent() {
-        if (mShowHistory) {
-            mManageButton.setText(mManageNotificationHistoryText);
-            mManageButton.setContentDescription(mManageNotificationHistoryText);
-        } else {
-            mManageButton.setText(mManageNotificationText);
-            mManageButton.setContentDescription(mManageNotificationText);
-        }
         if (FooterViewRefactor.isEnabled()) {
             updateClearAllButtonText();
             updateClearAllButtonDescription();
 
+            updateManageOrHistoryButtonText();
+            updateManageOrHistoryButtonDescription();
+
             updateMessageString();
             updateMessageIcon();
         } else {
@@ -285,6 +322,14 @@
             // `updateResources`, which will eventually be removed. There are, however, still
             // situations in which we want to update the views even if the resource IDs didn't
             // change, such as configuration changes.
+            if (mShowHistory) {
+                mManageOrHistoryButton.setText(mManageNotificationHistoryText);
+                mManageOrHistoryButton.setContentDescription(mManageNotificationHistoryText);
+            } else {
+                mManageOrHistoryButton.setText(mManageNotificationText);
+                mManageOrHistoryButton.setContentDescription(mManageNotificationText);
+            }
+
             mClearAllButton.setText(R.string.clear_all_notifications_text);
             mClearAllButton.setContentDescription(
                     mContext.getString(R.string.accessibility_clear_all));
@@ -297,6 +342,7 @@
 
     /** Whether the start button shows "History" (true) or "Manage" (false). */
     public boolean isHistoryShown() {
+        FooterViewRefactor.assertInLegacyMode();
         return mShowHistory;
     }
 
@@ -304,7 +350,9 @@
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
         updateColors();
-        updateResources();
+        if (!FooterViewRefactor.isEnabled()) {
+            updateResources();
+        }
         updateContent();
     }
 
@@ -328,23 +376,22 @@
         }
         mClearAllButton.setBackground(clearAllBg);
         mClearAllButton.setTextColor(onSurface);
-        mManageButton.setBackground(manageBg);
-        mManageButton.setTextColor(onSurface);
+        mManageOrHistoryButton.setBackground(manageBg);
+        mManageOrHistoryButton.setTextColor(onSurface);
         mSeenNotifsFooterTextView.setTextColor(onSurface);
         mSeenNotifsFooterTextView.setCompoundDrawableTintList(ColorStateList.valueOf(onSurface));
     }
 
     private void updateResources() {
+        FooterViewRefactor.assertInLegacyMode();
         mManageNotificationText = getContext().getString(R.string.manage_notifications_text);
         mManageNotificationHistoryText = getContext()
                 .getString(R.string.manage_notifications_history_text);
-        if (!FooterViewRefactor.isEnabled()) {
-            int unlockIconSize = getResources()
-                    .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size);
-            mSeenNotifsFilteredText = getContext().getString(R.string.unlock_to_see_notif_text);
-            mSeenNotifsFilteredIcon = getContext().getDrawable(R.drawable.ic_friction_lock_closed);
-            mSeenNotifsFilteredIcon.setBounds(0, 0, unlockIconSize, unlockIconSize);
-        }
+        int unlockIconSize = getResources()
+                .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size);
+        mSeenNotifsFilteredText = getContext().getString(R.string.unlock_to_see_notif_text);
+        mSeenNotifsFilteredIcon = getContext().getDrawable(R.drawable.ic_friction_lock_closed);
+        mSeenNotifsFilteredIcon.setBounds(0, 0, unlockIconSize, unlockIconSize);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
index e0eee96..9fb453a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
@@ -25,49 +25,136 @@
 import com.android.systemui.util.ui.stopAnimating
 import com.android.systemui.util.ui.value
 import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.launch
 
 /** Binds a [FooterView] to its [view model][FooterViewModel]. */
 object FooterViewBinder {
-    fun bind(
+    fun bindWhileAttached(
         footer: FooterView,
         viewModel: FooterViewModel,
         clearAllNotifications: View.OnClickListener,
+        launchNotificationSettings: View.OnClickListener,
+        launchNotificationHistory: View.OnClickListener,
     ): DisposableHandle {
+        return footer.repeatWhenAttached {
+            lifecycleScope.launch {
+                bind(
+                    footer,
+                    viewModel,
+                    clearAllNotifications,
+                    launchNotificationSettings,
+                    launchNotificationHistory
+                )
+            }
+        }
+    }
+
+    suspend fun bind(
+        footer: FooterView,
+        viewModel: FooterViewModel,
+        clearAllNotifications: View.OnClickListener,
+        launchNotificationSettings: View.OnClickListener,
+        launchNotificationHistory: View.OnClickListener
+    ) = coroutineScope {
+        launch {
+            bindClearAllButton(
+                footer,
+                viewModel,
+                clearAllNotifications,
+            )
+        }
+        launch {
+            bindManageOrHistoryButton(
+                footer,
+                viewModel,
+                launchNotificationSettings,
+                launchNotificationHistory
+            )
+        }
+        launch { bindMessage(footer, viewModel) }
+    }
+
+    private suspend fun bindClearAllButton(
+        footer: FooterView,
+        viewModel: FooterViewModel,
+        clearAllNotifications: View.OnClickListener,
+    ) = coroutineScope {
+        footer.setClearAllButtonClickListener(clearAllNotifications)
+
+        launch {
+            viewModel.clearAllButton.labelId.collect { textId ->
+                footer.setClearAllButtonText(textId)
+            }
+        }
+
+        launch {
+            viewModel.clearAllButton.accessibilityDescriptionId.collect { textId ->
+                footer.setClearAllButtonDescription(textId)
+            }
+        }
+
+        launch {
+            viewModel.clearAllButton.isVisible.collect { isVisible ->
+                if (isVisible.isAnimating) {
+                    footer.setClearAllButtonVisible(
+                        isVisible.value,
+                        /* animate = */ true,
+                    ) { _ ->
+                        isVisible.stopAnimating()
+                    }
+                } else {
+                    footer.setClearAllButtonVisible(
+                        isVisible.value,
+                        /* animate = */ false,
+                    )
+                }
+            }
+        }
+    }
+
+    private suspend fun bindManageOrHistoryButton(
+        footer: FooterView,
+        viewModel: FooterViewModel,
+        launchNotificationSettings: View.OnClickListener,
+        launchNotificationHistory: View.OnClickListener,
+    ) = coroutineScope {
+        launch {
+            viewModel.manageButtonShouldLaunchHistory.collect { shouldLaunchHistory ->
+                if (shouldLaunchHistory) {
+                    footer.setManageButtonClickListener(launchNotificationHistory)
+                } else {
+                    footer.setManageButtonClickListener(launchNotificationSettings)
+                }
+            }
+        }
+
+        launch {
+            viewModel.manageOrHistoryButton.labelId.collect { textId ->
+                footer.setManageOrHistoryButtonText(textId)
+            }
+        }
+
+        launch {
+            viewModel.clearAllButton.accessibilityDescriptionId.collect { textId ->
+                footer.setManageOrHistoryButtonDescription(textId)
+            }
+        }
+
+        // NOTE: The manage/history button is always visible as long as the footer is visible, no
+        //  need to update the visibility here.
+    }
+
+    private suspend fun bindMessage(
+        footer: FooterView,
+        viewModel: FooterViewModel,
+    ) = coroutineScope {
         // Bind the resource IDs
         footer.setMessageString(viewModel.message.messageId)
         footer.setMessageIcon(viewModel.message.iconId)
-        footer.setClearAllButtonText(viewModel.clearAllButton.labelId)
-        footer.setClearAllButtonDescription(viewModel.clearAllButton.accessibilityDescriptionId)
 
-        // Bind the click listeners
-        footer.setClearAllButtonClickListener(clearAllNotifications)
-
-        // Listen for visibility changes when the view is attached.
-        return footer.repeatWhenAttached {
-            lifecycleScope.launch {
-                viewModel.clearAllButton.isVisible.collect { isVisible ->
-                    if (isVisible.isAnimating) {
-                        footer.setClearAllButtonVisible(
-                            isVisible.value,
-                            /* animate = */ true,
-                        ) { _ ->
-                            isVisible.stopAnimating()
-                        }
-                    } else {
-                        footer.setClearAllButtonVisible(
-                            isVisible.value,
-                            /* animate = */ false,
-                        )
-                    }
-                }
-            }
-
-            lifecycleScope.launch {
-                viewModel.message.isVisible.collect { visible ->
-                    footer.setFooterLabelVisible(visible)
-                }
-            }
+        launch {
+            viewModel.message.isVisible.collect { visible -> footer.setFooterLabelVisible(visible) }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt
index 244555a..691dc42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt
@@ -21,7 +21,7 @@
 import kotlinx.coroutines.flow.Flow
 
 data class FooterButtonViewModel(
-    @StringRes val labelId: Int,
-    @StringRes val accessibilityDescriptionId: Int,
+    @StringRes val labelId: Flow<Int>,
+    @StringRes val accessibilityDescriptionId: Flow<Int>,
     val isVisible: Flow<AnimatedValue<Boolean>>,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
index e6b0abc..5111c11 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
@@ -19,30 +19,36 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.res.R
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
 import com.android.systemui.util.kotlin.sample
 import com.android.systemui.util.ui.AnimatableEvent
+import com.android.systemui.util.ui.AnimatedValue
 import com.android.systemui.util.ui.toAnimatedValueFlow
 import dagger.Module
 import dagger.Provides
 import java.util.Optional
 import javax.inject.Provider
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
 
 /** ViewModel for [FooterView]. */
 class FooterViewModel(
     activeNotificationsInteractor: ActiveNotificationsInteractor,
+    notificationSettingsInteractor: NotificationSettingsInteractor,
     seenNotificationsInteractor: SeenNotificationsInteractor,
     shadeInteractor: ShadeInteractor,
 ) {
     val clearAllButton: FooterButtonViewModel =
         FooterButtonViewModel(
-            labelId = R.string.clear_all_notifications_text,
-            accessibilityDescriptionId = R.string.accessibility_clear_all,
+            labelId = flowOf(R.string.clear_all_notifications_text),
+            accessibilityDescriptionId = flowOf(R.string.accessibility_clear_all),
             isVisible =
                 activeNotificationsInteractor.hasClearableNotifications
                     .sample(
@@ -59,6 +65,22 @@
                     .toAnimatedValueFlow(),
         )
 
+    val manageButtonShouldLaunchHistory =
+        notificationSettingsInteractor.isNotificationHistoryEnabled
+
+    private val manageOrHistoryButtonText: Flow<Int> =
+        manageButtonShouldLaunchHistory.map { shouldLaunchHistory ->
+            if (shouldLaunchHistory) R.string.manage_notifications_history_text
+            else R.string.manage_notifications_text
+        }
+
+    val manageOrHistoryButton: FooterButtonViewModel =
+        FooterButtonViewModel(
+            labelId = manageOrHistoryButtonText,
+            accessibilityDescriptionId = manageOrHistoryButtonText,
+            isVisible = flowOf(AnimatedValue.NotAnimating(true)),
+        )
+
     val message: FooterMessageViewModel =
         FooterMessageViewModel(
             messageId = R.string.unlock_to_see_notif_text,
@@ -73,6 +95,7 @@
     @SysUISingleton
     fun provideOptional(
         activeNotificationsInteractor: Provider<ActiveNotificationsInteractor>,
+        notificationSettingsInteractor: Provider<NotificationSettingsInteractor>,
         seenNotificationsInteractor: Provider<SeenNotificationsInteractor>,
         shadeInteractor: Provider<ShadeInteractor>,
     ): Optional<FooterViewModel> {
@@ -80,6 +103,7 @@
             Optional.of(
                 FooterViewModel(
                     activeNotificationsInteractor.get(),
+                    notificationSettingsInteractor.get(),
                     seenNotificationsInteractor.get(),
                     shadeInteractor.get()
                 )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index b9afb14..5e0110b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -4698,6 +4698,7 @@
      * this will return false.
      **/
     public boolean isHistoryShown() {
+        FooterViewRefactor.assertInLegacyMode();
         return mFooterView != null && mFooterView.isHistoryShown();
     }
 
@@ -4710,10 +4711,10 @@
         }
         mFooterView = footerView;
         addView(mFooterView, index);
-        if (mManageButtonClickListener != null) {
-            mFooterView.setManageButtonClickListener(mManageButtonClickListener);
-        }
         if (!FooterViewRefactor.isEnabled()) {
+            if (mManageButtonClickListener != null) {
+                mFooterView.setManageButtonClickListener(mManageButtonClickListener);
+            }
             mFooterView.setClearAllButtonClickListener(v -> {
                 if (mFooterClearAllListener != null) {
                     mFooterClearAllListener.onClearAll();
@@ -4794,8 +4795,8 @@
         }
         boolean animate = mIsExpanded && mAnimationsEnabled;
         mFooterView.setVisible(visible, animate);
-        mFooterView.showHistory(showHistory);
         if (!FooterViewRefactor.isEnabled()) {
+            mFooterView.showHistory(showHistory);
             mFooterView.setClearAllButtonVisible(showDismissView, animate);
             mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications);
         }
@@ -5490,6 +5491,7 @@
      * Register a {@link View.OnClickListener} to be invoked when the Manage button is clicked.
      */
     public void setManageButtonClickListener(@Nullable OnClickListener listener) {
+        FooterViewRefactor.assertInLegacyMode();
         mManageButtonClickListener = listener;
         if (mFooterView != null) {
             mFooterView.setManageButtonClickListener(mManageButtonClickListener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index ed26677..a2ff406 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -21,8 +21,9 @@
 
 import static com.android.app.animation.Interpolators.STANDARD;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
+import static com.android.server.notification.Flags.screenshareNotificationHiding;
 import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
-import static com.android.systemui.Flags.screenshareNotificationHiding;
+import static com.android.systemui.Flags.nsslFalsingFix;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnEmptySpaceClickListener;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnOverscrollTopChangedListener;
@@ -845,11 +846,13 @@
         mView.setKeyguardBypassEnabled(mKeyguardBypassController.getBypassEnabled());
         mKeyguardBypassController
                 .registerOnBypassStateChangedListener(mView::setKeyguardBypassEnabled);
-        mView.setManageButtonClickListener(v -> {
-            if (mNotificationActivityStarter != null) {
-                mNotificationActivityStarter.startHistoryIntent(v, mView.isHistoryShown());
-            }
-        });
+        if (!FooterViewRefactor.isEnabled()) {
+            mView.setManageButtonClickListener(v -> {
+                if (mNotificationActivityStarter != null) {
+                    mNotificationActivityStarter.startHistoryIntent(v, mView.isHistoryShown());
+                }
+            });
+        }
 
         mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
         mHeadsUpManager.setAnimationStateHandler(mView::setHeadsUpGoingAwayAnimationsAllowed);
@@ -2052,7 +2055,7 @@
             }
             boolean horizontalSwipeWantsIt = false;
             boolean scrollerWantsIt = false;
-            if (KeyguardShadeMigrationNssl.isEnabled()) {
+            if (nsslFalsingFix() || KeyguardShadeMigrationNssl.isEnabled()) {
                 // Reverse the order relative to the else statement. onScrollTouch will reset on an
                 // UP event, causing horizontalSwipeWantsIt to be set to true on vertical swipes.
                 if (mLongPressedView == null && !mView.isBeingDragged()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index 44a7e7e..4d65b9d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.notification.NotificationActivityStarter
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
 import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterViewBinder
@@ -45,6 +46,7 @@
 import com.android.systemui.util.kotlin.getOrNull
 import java.util.Optional
 import javax.inject.Inject
+import javax.inject.Provider
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
@@ -58,9 +60,11 @@
     private val configuration: ConfigurationState,
     private val falsingManager: FalsingManager,
     private val iconAreaController: NotificationIconAreaController,
+    private val loggerOptional: Optional<NotificationStatsLogger>,
     private val metricsLogger: MetricsLogger,
     private val nicBinder: NotificationIconContainerShelfViewBinder,
-    private val loggerOptional: Optional<NotificationStatsLogger>,
+    // Using a provider to avoid a circular dependency.
+    private val notificationActivityStarter: Provider<NotificationActivityStarter>,
     private val viewModel: NotificationListViewModel,
 ) {
 
@@ -115,7 +119,7 @@
             ) { footerView: FooterView ->
                 traceSection("bind FooterView") {
                     val disposableHandle =
-                        FooterViewBinder.bind(
+                        FooterViewBinder.bindWhileAttached(
                             footerView,
                             footerViewModel,
                             clearAllNotifications = {
@@ -124,6 +128,16 @@
                                 )
                                 parentView.clearAllNotifications()
                             },
+                            launchNotificationSettings = { view ->
+                                notificationActivityStarter
+                                    .get()
+                                    .startHistoryIntent(view, /* showHistory = */ false)
+                            },
+                            launchNotificationHistory = { view ->
+                                notificationActivityStarter
+                                    .get()
+                                    .startHistoryIntent(view, /* showHistory = */ true)
+                            },
                         )
                     parentView.setFooterView(footerView)
                     return@reinflateAndBindLatest disposableHandle
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
index b0192c0..11e374f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.policy;
 
-import static com.android.systemui.Flags.screenshareNotificationHiding;
+import static com.android.server.notification.Flags.screenshareNotificationHiding;
 
 import android.media.projection.MediaProjectionInfo;
 import android.media.projection.MediaProjectionManager;
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SharedPreferencesExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/SharedPreferencesExt.kt
new file mode 100644
index 0000000..ab6a37b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SharedPreferencesExt.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.kotlin
+
+import android.content.SharedPreferences
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.mapNotNull
+
+object SharedPreferencesExt {
+    /**
+     * Returns a flow of [Unit] that is invoked each time shared preference is updated.
+     *
+     * @param key Optional key to limit updates to a particular key.
+     */
+    fun SharedPreferences.observe(key: String? = null): Flow<Unit> =
+        conflatedCallbackFlow {
+                val listener =
+                    SharedPreferences.OnSharedPreferenceChangeListener { _, key -> trySend(key) }
+                registerOnSharedPreferenceChangeListener(listener)
+                awaitClose { unregisterOnSharedPreferenceChangeListener(listener) }
+            }
+            .mapNotNull { changedKey -> if ((key ?: changedKey) == changedKey) Unit else null }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
index 8d5e55a..ff1daea 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.volume.dagger
 
+import android.content.Context
 import android.media.AudioManager
 import com.android.settingslib.volume.data.repository.AudioRepository
 import com.android.settingslib.volume.data.repository.AudioRepositoryImpl
@@ -35,10 +36,12 @@
 
         @Provides
         fun provideAudioRepository(
+            @Application context: Context,
             audioManager: AudioManager,
             @Background coroutineContext: CoroutineContext,
             @Application coroutineScope: CoroutineScope,
-        ): AudioRepository = AudioRepositoryImpl(audioManager, coroutineContext, coroutineScope)
+        ): AudioRepository =
+            AudioRepositoryImpl(context, audioManager, coroutineContext, coroutineScope)
 
         @Provides
         fun provideAudioModeInteractor(repository: AudioRepository): AudioModeInteractor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt
index 8f0e910..8fbeb6f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.biometrics.data.repository
 
 import android.hardware.biometrics.AuthenticationStateListener
+import android.hardware.biometrics.BiometricFingerprintConstants
 import android.hardware.biometrics.BiometricManager
 import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_BP
 import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD
@@ -24,11 +25,13 @@
 import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS
 import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING
 import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_FIND_SENSOR
+import android.hardware.biometrics.BiometricSourceType
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.shared.model.AuthenticationReason
 import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
 import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
 import com.android.systemui.util.mockito.withArgCaptor
 import com.google.common.truth.Truth.assertThat
@@ -167,6 +170,28 @@
             listener.onAuthenticationStopped()
             assertThat(fingerprintAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning)
         }
+
+    @Test
+    fun updatesFingerprintAcquiredStatusWhenBiometricPromptAuthenticationAcquired() =
+        testScope.runTest {
+            val fingerprintAcquiredStatus by collectLastValue(underTest.fingerprintAcquiredStatus)
+            runCurrent()
+
+            val listener = biometricManager.captureListener()
+            listener.onAuthenticationAcquired(
+                BiometricSourceType.FINGERPRINT,
+                REASON_AUTH_BP,
+                BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
+            )
+
+            assertThat(fingerprintAcquiredStatus)
+                .isEqualTo(
+                    AcquiredFingerprintAuthenticationStatus(
+                        AuthenticationReason.BiometricPromptAuthentication,
+                        BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
+                    )
+                )
+        }
 }
 
 private fun BiometricManager.captureListener() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt
index d7b7d79..5c34fd9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt
@@ -19,12 +19,14 @@
 import android.app.ActivityManager
 import android.app.ActivityTaskManager
 import android.content.ComponentName
+import android.hardware.biometrics.BiometricFingerprintConstants
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository
 import com.android.systemui.biometrics.shared.model.AuthenticationReason
 import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
 import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -162,6 +164,27 @@
             )
             assertThat(sfpsAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning)
         }
+
+    @Test
+    fun updatesFingerprintAcquiredStatusWhenBiometricPromptAuthenticationAcquired() =
+        testScope.runTest {
+            val fingerprintAcquiredStatus by collectLastValue(underTest.fingerprintAcquiredStatus)
+            runCurrent()
+
+            biometricStatusRepository.setFingerprintAcquiredStatus(
+                AcquiredFingerprintAuthenticationStatus(
+                    AuthenticationReason.BiometricPromptAuthentication,
+                    BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
+                )
+            )
+            assertThat(fingerprintAcquiredStatus)
+                .isEqualTo(
+                    AcquiredFingerprintAuthenticationStatus(
+                        AuthenticationReason.BiometricPromptAuthentication,
+                        BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
+                    )
+                )
+        }
 }
 
 private fun fpSettingsTask() = settingsTask(".biometrics.fingerprint.FingerprintSettings")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
index 3603c3c..5509c04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
@@ -58,6 +58,7 @@
 import com.android.systemui.bouncer.ui.BouncerView
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFingerprintAuthInteractor
 import com.android.systemui.display.data.repository.FakeDisplayRepository
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
@@ -100,6 +101,7 @@
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.any
+import org.mockito.Mockito.inOrder
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.spy
@@ -253,7 +255,8 @@
         sideFpsProgressBarViewModel =
             SideFpsProgressBarViewModel(
                 mContext,
-                mock(),
+                biometricStatusInteractor,
+                kosmos.deviceEntryFingerprintAuthInteractor,
                 sfpsSensorInteractor,
                 kosmos.dozeServiceHost,
                 kosmos.keyguardInteractor,
@@ -426,6 +429,54 @@
         }
     }
 
+    // On progress bar shown - hide indicator
+    // On progress bar hidden - show indicator
+    @Test
+    fun verifyIndicatorProgressBarInteraction() {
+        testScope.runTest {
+            // Pre-auth conditions
+            setupTestConfiguration(
+                DeviceConfig.X_ALIGNED,
+                rotation = DisplayRotation.ROTATION_0,
+                isInRearDisplayMode = false
+            )
+            biometricStatusRepository.setFingerprintAuthenticationReason(
+                AuthenticationReason.NotRunning
+            )
+            sideFpsProgressBarViewModel.setVisible(false)
+
+            // Show primary bouncer
+            updatePrimaryBouncer(
+                isShowing = true,
+                isAnimatingAway = false,
+                fpsDetectionRunning = true,
+                isUnlockingWithFpAllowed = true
+            )
+            runCurrent()
+
+            val inOrder = inOrder(windowManager)
+
+            // Verify indicator shown
+            inOrder.verify(windowManager).addView(any(), any())
+
+            // Set progress bar visible
+            sideFpsProgressBarViewModel.setVisible(true)
+
+            runCurrent()
+
+            // Verify indicator hidden
+            inOrder.verify(windowManager).removeView(any())
+
+            // Set progress bar invisible
+            sideFpsProgressBarViewModel.setVisible(false)
+
+            runCurrent()
+
+            // Verify indicator shown
+            inOrder.verify(windowManager).addView(any(), any())
+        }
+    }
+
     private fun updatePrimaryBouncer(
         isShowing: Boolean,
         isAnimatingAway: Boolean,
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 6a9c881..2e94d38 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
@@ -81,6 +81,7 @@
 private const val CHALLENGE = 2L
 private const val DELAY = 1000L
 private const val OP_PACKAGE_NAME = "biometric.testapp"
+private const val OP_PACKAGE_NAME_NO_ICON = "biometric.testapp.noicon"
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -1246,6 +1247,14 @@
         }
 
     @Test
+    fun logoIsNullIfPackageNameNotFound() =
+        runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
+            mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+            val logo by collectLastValue(viewModel.logo)
+            assertThat(logo).isNull()
+        }
+
+    @Test
     fun defaultLogoIfNoLogoSet() = runGenericTest {
         mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
         val logo by collectLastValue(viewModel.logo)
@@ -1291,7 +1300,8 @@
         contentView: PromptContentView? = null,
         logoRes: Int = -1,
         logoBitmap: Bitmap? = null,
-        block: suspend TestScope.() -> Unit
+        packageName: String = OP_PACKAGE_NAME,
+        block: suspend TestScope.() -> Unit,
     ) {
         selector.initializePrompt(
             requireConfirmation = testCase.confirmationRequested,
@@ -1302,6 +1312,7 @@
             contentViewFromApp = contentView,
             logoResFromApp = logoRes,
             logoBitmapFromApp = logoBitmap,
+            packageName = packageName,
         )
 
         // put the view model in the initial authenticating state, unless explicitly skipped
@@ -1481,6 +1492,7 @@
     contentViewFromApp: PromptContentView? = null,
     logoResFromApp: Int = -1,
     logoBitmapFromApp: Bitmap? = null,
+    packageName: String = OP_PACKAGE_NAME,
 ) {
     val info =
         PromptInfo().apply {
@@ -1500,7 +1512,7 @@
         USER_ID,
         CHALLENGE,
         BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face),
-        OP_PACKAGE_NAME,
+        packageName,
     )
 }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
index 3c43031..2014755 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.biometrics.ui.viewmodel
 
-import android.app.ActivityTaskManager
 import android.content.res.Configuration.UI_MODE_NIGHT_NO
 import android.content.res.Configuration.UI_MODE_NIGHT_YES
 import android.graphics.Color
@@ -39,10 +38,10 @@
 import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository
 import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
 import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
-import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor
-import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractorImpl
+import com.android.systemui.biometrics.data.repository.biometricStatusRepository
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
 import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor
+import com.android.systemui.biometrics.domain.interactor.biometricStatusInteractor
 import com.android.systemui.biometrics.shared.model.AuthenticationReason
 import com.android.systemui.biometrics.shared.model.DisplayRotation
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
@@ -80,7 +79,6 @@
 import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -109,7 +107,6 @@
     private val kosmos = testKosmos()
     @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
 
-    @Mock private lateinit var activityTaskManager: ActivityTaskManager
     @Mock private lateinit var faceAuthInteractor: DeviceEntryFaceAuthInteractor
     @Mock
     private lateinit var fingerprintInteractiveToAuthProvider: FingerprintInteractiveToAuthProvider
@@ -147,7 +144,6 @@
         context.getColor(com.android.settingslib.color.R.color.settingslib_color_blue400)
 
     private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
-    private lateinit var biometricStatusInteractor: BiometricStatusInteractor
     private lateinit var deviceEntrySideFpsOverlayInteractor: DeviceEntrySideFpsOverlayInteractor
     private lateinit var displayStateInteractor: DisplayStateInteractorImpl
     private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
@@ -184,6 +180,7 @@
             .thenReturn(
                 Display(mock(DisplayManagerGlobal::class.java), 1, contextDisplayInfo, resources)
             )
+        kosmos.biometricStatusRepository = biometricStatusRepository
 
         alternateBouncerInteractor =
             AlternateBouncerInteractor(
@@ -197,9 +194,6 @@
                 testScope.backgroundScope,
             )
 
-        biometricStatusInteractor =
-            BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository)
-
         displayStateInteractor =
             DisplayStateInteractorImpl(
                 testScope.backgroundScope,
@@ -256,6 +250,7 @@
         sideFpsProgressBarViewModel =
             SideFpsProgressBarViewModel(
                 mContext,
+                kosmos.biometricStatusInteractor,
                 kosmos.deviceEntryFingerprintAuthInteractor,
                 sfpsSensorInteractor,
                 kosmos.dozeServiceHost,
@@ -263,13 +258,13 @@
                 displayStateInteractor,
                 kosmos.testDispatcher,
                 testScope.backgroundScope,
-                kosmos.powerInteractor,
+                kosmos.powerInteractor
             )
 
         underTest =
             SideFpsOverlayViewModel(
                 mContext,
-                biometricStatusInteractor,
+                kosmos.biometricStatusInteractor,
                 deviceEntrySideFpsOverlayInteractor,
                 displayStateInteractor,
                 sfpsSensorInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
index c98d537..de455f63 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -34,11 +34,12 @@
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.panels.AuthorizedPanelsRepository
-import com.android.systemui.controls.panels.FakeSelectedComponentRepository
+import com.android.systemui.controls.panels.selectedComponentRepository
 import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.testKosmos
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
@@ -69,12 +70,13 @@
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 import java.io.File
-import java.util.*
+import java.util.Optional
 import java.util.function.Consumer
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 class ControlsControllerImplTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
 
     @Mock
     private lateinit var uiController: ControlsUiController
@@ -109,8 +111,6 @@
     private lateinit var listingCallbackCaptor:
             ArgumentCaptor<ControlsListingController.ControlsListingCallback>
 
-    private val preferredPanelRepository = FakeSelectedComponentRepository()
-
     private lateinit var delayableExecutor: FakeExecutor
     private lateinit var controller: ControlsControllerImpl
     private lateinit var canceller: DidRunRunnable
@@ -171,7 +171,7 @@
                 wrapper,
                 delayableExecutor,
                 uiController,
-                preferredPanelRepository,
+                kosmos.selectedComponentRepository,
                 bindingController,
                 listingController,
                 userFileManager,
@@ -225,7 +225,7 @@
                 mContext,
                 delayableExecutor,
                 uiController,
-                preferredPanelRepository,
+                kosmos.selectedComponentRepository,
                 bindingController,
                 listingController,
                 userFileManager,
@@ -245,7 +245,7 @@
                 mContext,
                 delayableExecutor,
                 uiController,
-                preferredPanelRepository,
+                kosmos.selectedComponentRepository,
                 bindingController,
                 listingController,
                 userFileManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
index 4828ba3..18ce4a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
@@ -18,36 +18,40 @@
 package com.android.systemui.controls.panels
 
 import android.content.SharedPreferences
+import android.content.pm.UserInfo
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.settings.UserFileManager
-import com.android.systemui.settings.UserTracker
+import com.android.systemui.settings.fakeUserTracker
+import com.android.systemui.testKosmos
 import com.android.systemui.util.FakeSharedPreferences
-import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import java.io.File
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class AuthorizedPanelsRepositoryImplTest : SysuiTestCase() {
+    val kosmos = testKosmos()
+    val testScope = kosmos.testScope
 
-    @Mock private lateinit var userTracker: UserTracker
+    private lateinit var userTracker: FakeUserTracker
 
     @Before
     fun setUp() {
-        MockitoAnnotations.initMocks(this)
         mContext.orCreateTestableResources.addOverride(
             R.array.config_controlsPreferredPackages,
             arrayOf<String>()
         )
-        whenever(userTracker.userId).thenReturn(0)
+        userTracker = kosmos.fakeUserTracker.apply { set(listOf(PRIMARY_USER, SECONDARY_USER), 0) }
     }
 
     @Test
@@ -91,7 +95,7 @@
         val repository = createRepository(fileManager)
 
         assertThat(repository.getAuthorizedPanels()).containsExactly(TEST_PACKAGE)
-        whenever(userTracker.userId).thenReturn(1)
+        userTracker.set(listOf(SECONDARY_USER), 0)
         assertThat(repository.getAuthorizedPanels()).isEmpty()
     }
 
@@ -127,6 +131,51 @@
         assertThat(sharedPrefs.getStringSet(KEY, null)).isEmpty()
     }
 
+    @Test
+    fun observeAuthorizedPanels() =
+        testScope.runTest {
+            val sharedPrefs = FakeSharedPreferences()
+            val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs))
+            val repository = createRepository(fileManager)
+
+            val authorizedPanels by
+                collectLastValue(repository.observeAuthorizedPanels(PRIMARY_USER.userHandle))
+            assertThat(authorizedPanels).isEmpty()
+
+            repository.addAuthorizedPanels(setOf(TEST_PACKAGE))
+            assertThat(authorizedPanels).containsExactly(TEST_PACKAGE)
+
+            repository.removeAuthorizedPanels(setOf(TEST_PACKAGE))
+            assertThat(authorizedPanels).isEmpty()
+        }
+
+    @Test
+    fun observeAuthorizedPanelsForAnotherUser() =
+        testScope.runTest {
+            val fileManager =
+                FakeUserFileManager(
+                    mapOf(
+                        0 to FakeSharedPreferences(),
+                        1 to FakeSharedPreferences(),
+                    )
+                )
+            val repository = createRepository(fileManager)
+
+            val authorizedPanels by
+                collectLastValue(repository.observeAuthorizedPanels(SECONDARY_USER.userHandle))
+            assertThat(authorizedPanels).isEmpty()
+
+            // Primary user is active, add authorized panels.
+            repository.addAuthorizedPanels(setOf(TEST_PACKAGE))
+            assertThat(authorizedPanels).isEmpty()
+
+            // Make secondary user active and add authorized panels again.
+            userTracker.set(listOf(PRIMARY_USER, SECONDARY_USER), 1)
+            assertThat(authorizedPanels).isEmpty()
+            repository.addAuthorizedPanels(setOf(TEST_PACKAGE))
+            assertThat(authorizedPanels).containsExactly(TEST_PACKAGE)
+        }
+
     private fun createRepository(userFileManager: UserFileManager): AuthorizedPanelsRepositoryImpl {
         return AuthorizedPanelsRepositoryImpl(mContext, userFileManager, userTracker)
     }
@@ -153,5 +202,9 @@
         private const val FILE_NAME = "controls_prefs"
         private const val KEY = "authorized_panels"
         private const val TEST_PACKAGE = "package"
+        private val PRIMARY_USER =
+            UserInfo(/* id= */ 0, /* name= */ "primary user", /* flags= */ UserInfo.FLAG_MAIN)
+        private val SECONDARY_USER =
+            UserInfo(/* id= */ 1, /* name= */ "secondary user", /* flags= */ 0)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
index b463adf..a7e7ba9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
@@ -23,8 +23,6 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.settings.UserFileManager
@@ -74,7 +72,6 @@
     @Mock private lateinit var userTracker: UserTracker
     private lateinit var userFileManager: UserFileManager
 
-    private val featureFlags = FakeFeatureFlags()
     // under test
     private lateinit var repository: SelectedComponentRepository
 
@@ -95,11 +92,9 @@
                 )
             repository =
                 SelectedComponentRepositoryImpl(
-                    userFileManager,
-                    userTracker,
-                    featureFlags,
+                    userFileManager = userFileManager,
+                    userTracker = userTracker,
                     bgDispatcher = testDispatcher,
-                    applicationScope = applicationCoroutineScope
                 )
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
index bcef67e..94ea799 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
@@ -38,8 +38,8 @@
 import com.android.systemui.controls.dagger.ControlsComponent
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.panels.AuthorizedPanelsRepository
-import com.android.systemui.controls.panels.FakeSelectedComponentRepository
 import com.android.systemui.controls.panels.SelectedComponentRepository
+import com.android.systemui.controls.panels.selectedComponentRepository
 import com.android.systemui.controls.ui.SelectedItem
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testDispatcher
@@ -87,7 +87,7 @@
     @Mock private lateinit var userManager: UserManager
     @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
 
-    private lateinit var preferredPanelsRepository: FakeSelectedComponentRepository
+    private lateinit var preferredPanelsRepository: SelectedComponentRepository
 
     private lateinit var fakeExecutor: FakeExecutor
 
@@ -99,7 +99,7 @@
         whenever(userTracker.userHandle).thenReturn(UserHandle.of(1))
 
         fakeExecutor = FakeExecutor(FakeSystemClock())
-        preferredPanelsRepository = FakeSelectedComponentRepository()
+        preferredPanelsRepository = kosmos.selectedComponentRepository
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
index 36ae0c7..8f3813d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -43,8 +43,8 @@
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.management.ControlsProviderSelectorActivity
 import com.android.systemui.controls.panels.AuthorizedPanelsRepository
-import com.android.systemui.controls.panels.FakeSelectedComponentRepository
 import com.android.systemui.controls.panels.SelectedComponentRepository
+import com.android.systemui.controls.panels.selectedComponentRepository
 import com.android.systemui.controls.settings.FakeControlsSettingsRepository
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
@@ -53,6 +53,7 @@
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
 import com.android.systemui.util.FakeSystemUIDialogController
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
@@ -85,6 +86,8 @@
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
 class ControlsUiControllerImplTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
     @Mock lateinit var controlsController: ControlsController
     @Mock lateinit var controlsListingController: ControlsListingController
     @Mock lateinit var controlActionCoordinator: ControlActionCoordinator
@@ -100,7 +103,7 @@
     @Mock lateinit var packageManager: PackageManager
     @Mock lateinit var systemUIDialogFactory: SystemUIDialog.Factory
 
-    private val preferredPanelRepository = FakeSelectedComponentRepository()
+    private val preferredPanelRepository = kosmos.selectedComponentRepository
     private lateinit var fakeDialogController: FakeSystemUIDialogController
     private val uiExecutor = FakeExecutor(FakeSystemClock())
     private val bgExecutor = FakeExecutor(FakeSystemClock())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index db5bd9b..0d1e874 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -182,6 +182,8 @@
     @Mock
     private UiEventLogger mUiEventLogger;
     @Mock
+    private NavBarButtonClickLogger mNavBarButtonClickLogger;
+    @Mock
     private ViewTreeObserver mViewTreeObserver;
     NavBarHelper mNavBarHelper;
     @Mock
@@ -596,7 +598,8 @@
                 mUserContextProvider,
                 mWakefulnessLifecycle,
                 mTaskStackChangeListeners,
-                new FakeDisplayTracker(mContext)));
+                new FakeDisplayTracker(mContext),
+                mNavBarButtonClickLogger));
     }
 
     private void processAllMessages() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
index 50349be..0dd988d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
@@ -82,4 +82,22 @@
             underTest.setShowNotificationsOnLockscreenEnabled(false)
             assertThat(showNotifs).isEqualTo(false)
         }
+
+    @Test
+    fun testGetIsNotificationHistoryEnabled() =
+        testScope.runTest {
+            val historyEnabled by collectLastValue(underTest.isNotificationHistoryEnabled)
+
+            secureSettingsRepository.setInt(
+                name = Settings.Secure.NOTIFICATION_HISTORY_ENABLED,
+                value = 1,
+            )
+            assertThat(historyEnabled).isEqualTo(true)
+
+            secureSettingsRepository.setInt(
+                name = Settings.Secure.NOTIFICATION_HISTORY_ENABLED,
+                value = 0,
+            )
+            assertThat(historyEnabled).isEqualTo(false)
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
index 350ed2d..7d99d05 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
@@ -21,7 +21,7 @@
 import android.service.notification.StatusBarNotification
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING
+import com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
index 57dac3a..cac4a8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
@@ -17,10 +17,13 @@
 package com.android.systemui.statusbar.notification.footer.ui.view;
 
 import static com.android.systemui.log.LogAssertKt.assertLogsWtf;
+
 import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertTrue;
+
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
@@ -95,6 +98,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void setHistoryShown() {
         mView.showHistory(true);
         assertTrue(mView.isHistoryShown());
@@ -103,6 +107,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void setHistoryNotShown() {
         mView.showHistory(false);
         assertFalse(mView.isHistoryShown());
@@ -128,6 +133,62 @@
 
     @Test
     @EnableFlags(FooterViewRefactor.FLAG_NAME)
+    public void testSetManageOrHistoryButtonText_resourceOnlyFetchedOnce() {
+        int resId = R.string.manage_notifications_history_text;
+        mView.setManageOrHistoryButtonText(resId);
+        verify(mSpyContext).getString(eq(resId));
+
+        clearInvocations(mSpyContext);
+
+        assertThat(((TextView) mView.findViewById(R.id.manage_text))
+                .getText().toString()).contains("History");
+
+        // Set it a few more times, it shouldn't lead to the resource being fetched again
+        mView.setManageOrHistoryButtonText(resId);
+        mView.setManageOrHistoryButtonText(resId);
+
+        verify(mSpyContext, never()).getString(anyInt());
+    }
+
+    @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
+    public void testSetManageOrHistoryButtonText_expectsFlagEnabled() {
+        clearInvocations(mSpyContext);
+        int resId = R.string.manage_notifications_history_text;
+        assertLogsWtf(() -> mView.setManageOrHistoryButtonText(resId));
+        verify(mSpyContext, never()).getString(anyInt());
+    }
+
+    @Test
+    @EnableFlags(FooterViewRefactor.FLAG_NAME)
+    public void testSetManageOrHistoryButtonDescription_resourceOnlyFetchedOnce() {
+        int resId = R.string.manage_notifications_history_text;
+        mView.setManageOrHistoryButtonDescription(resId);
+        verify(mSpyContext).getString(eq(resId));
+
+        clearInvocations(mSpyContext);
+
+        assertThat(((TextView) mView.findViewById(R.id.manage_text))
+                .getContentDescription().toString()).contains("History");
+
+        // Set it a few more times, it shouldn't lead to the resource being fetched again
+        mView.setManageOrHistoryButtonDescription(resId);
+        mView.setManageOrHistoryButtonDescription(resId);
+
+        verify(mSpyContext, never()).getString(anyInt());
+    }
+
+    @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
+    public void testSetManageOrHistoryButtonDescription_expectsFlagEnabled() {
+        clearInvocations(mSpyContext);
+        int resId = R.string.accessibility_clear_all;
+        assertLogsWtf(() -> mView.setManageOrHistoryButtonDescription(resId));
+        verify(mSpyContext, never()).getString(anyInt());
+    }
+
+    @Test
+    @EnableFlags(FooterViewRefactor.FLAG_NAME)
     public void testSetClearAllButtonText_resourceOnlyFetchedOnce() {
         int resId = R.string.clear_all_notifications_text;
         mView.setClearAllButtonText(resId);
@@ -150,7 +211,7 @@
     public void testSetClearAllButtonText_expectsFlagEnabled() {
         clearInvocations(mSpyContext);
         int resId = R.string.clear_all_notifications_text;
-        assertLogsWtf(()-> mView.setClearAllButtonText(resId));
+        assertLogsWtf(() -> mView.setClearAllButtonText(resId));
         verify(mSpyContext, never()).getString(anyInt());
     }
 
@@ -178,7 +239,7 @@
     public void testSetClearAllButtonDescription_expectsFlagEnabled() {
         clearInvocations(mSpyContext);
         int resId = R.string.accessibility_clear_all;
-        assertLogsWtf(()-> mView.setClearAllButtonDescription(resId));
+        assertLogsWtf(() -> mView.setClearAllButtonDescription(resId));
         verify(mSpyContext, never()).getString(anyInt());
     }
 
@@ -206,7 +267,7 @@
     public void testSetMessageString_expectsFlagEnabled() {
         clearInvocations(mSpyContext);
         int resId = R.string.unlock_to_see_notif_text;
-        assertLogsWtf(()-> mView.setMessageString(resId));
+        assertLogsWtf(() -> mView.setMessageString(resId));
         verify(mSpyContext, never()).getString(anyInt());
     }
 
@@ -231,7 +292,7 @@
     public void testSetMessageIcon_expectsFlagEnabled() {
         clearInvocations(mSpyContext);
         int resId = R.drawable.ic_friction_lock_closed;
-        assertLogsWtf(()-> mView.setMessageIcon(resId));
+        assertLogsWtf(() -> mView.setMessageIcon(resId));
         verify(mSpyContext, never()).getDrawable(anyInt());
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
index 8ab13f5..620d972 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
@@ -14,109 +14,61 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.statusbar.notification.footer.ui.viewmodel
 
 import android.platform.test.annotations.EnableFlags
+import android.provider.Settings
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.collectLastValue
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.shared.model.StatusBarState
-import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.powerRepository
 import com.android.systemui.power.shared.model.WakeSleepReason
 import com.android.systemui.power.shared.model.WakefulnessState
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.res.R
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shared.settings.data.repository.fakeSecureSettingsRepository
 import com.android.systemui.statusbar.notification.collection.render.NotifStats
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule
-import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.user.domain.interactor.HeadlessSystemUserModeModule
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.testKosmos
 import com.android.systemui.util.ui.isAnimating
 import com.android.systemui.util.ui.value
 import com.google.common.truth.Truth.assertThat
-import dagger.BindsInstance
-import dagger.Component
-import java.util.Optional
-import org.junit.Before
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 @EnableFlags(FooterViewRefactor.FLAG_NAME)
 class FooterViewModelTest : SysuiTestCase() {
-    private lateinit var footerViewModel: FooterViewModel
-
-    @SysUISingleton
-    @Component(
-        modules =
-            [
-                SysUITestModule::class,
-                ActivatableNotificationViewModelModule::class,
-                FooterViewModelModule::class,
-                HeadlessSystemUserModeModule::class,
-            ]
-    )
-    interface TestComponent : SysUITestComponent<Optional<FooterViewModel>> {
-        val activeNotificationListRepository: ActiveNotificationListRepository
-        val configurationRepository: FakeConfigurationRepository
-        val keyguardRepository: FakeKeyguardRepository
-        val keyguardTransitionRepository: FakeKeyguardTransitionRepository
-        val shadeRepository: FakeShadeRepository
-        val powerRepository: FakePowerRepository
-
-        @Component.Factory
-        interface Factory {
-            fun create(
-                @BindsInstance test: SysuiTestCase,
-                featureFlags: FakeFeatureFlagsClassicModule,
-                mocks: TestMocksModule,
-            ): TestComponent
+    private val kosmos =
+        testKosmos().apply {
+            fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
         }
-    }
+    private val testScope = kosmos.testScope
+    private val activeNotificationListRepository = kosmos.activeNotificationListRepository
+    private val fakeKeyguardRepository = kosmos.fakeKeyguardRepository
+    private val shadeRepository = kosmos.shadeRepository
+    private val powerRepository = kosmos.powerRepository
+    private val fakeSecureSettingsRepository = kosmos.fakeSecureSettingsRepository
 
-    private val dozeParameters: DozeParameters = mock()
-
-    private val testComponent: TestComponent =
-        DaggerFooterViewModelTest_TestComponent.factory()
-            .create(
-                test = this,
-                featureFlags =
-                    FakeFeatureFlagsClassicModule {
-                        set(com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER, true)
-                    },
-                mocks =
-                    TestMocksModule(
-                        dozeParameters = dozeParameters,
-                    )
-            )
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        // The underTest in the component is Optional, because that matches the provider we
-        // currently have for the footer view model.
-        footerViewModel = testComponent.underTest.get()
-    }
+    val underTest = kosmos.footerViewModel
 
     @Test
     fun testMessageVisible_whenFilteredNotifications() =
-        testComponent.runTest {
-            val visible by collectLastValue(footerViewModel.message.isVisible)
+        testScope.runTest {
+            val visible by collectLastValue(underTest.message.isVisible)
 
             activeNotificationListRepository.hasFilteredOutSeenNotifications.value = true
 
@@ -125,8 +77,8 @@
 
     @Test
     fun testMessageVisible_whenNoFilteredNotifications() =
-        testComponent.runTest {
-            val visible by collectLastValue(footerViewModel.message.isVisible)
+        testScope.runTest {
+            val visible by collectLastValue(underTest.message.isVisible)
 
             activeNotificationListRepository.hasFilteredOutSeenNotifications.value = false
 
@@ -135,8 +87,8 @@
 
     @Test
     fun testClearAllButtonVisible_whenHasClearableNotifs() =
-        testComponent.runTest {
-            val visible by collectLastValue(footerViewModel.clearAllButton.isVisible)
+        testScope.runTest {
+            val visible by collectLastValue(underTest.clearAllButton.isVisible)
 
             activeNotificationListRepository.notifStats.value =
                 NotifStats(
@@ -153,8 +105,8 @@
 
     @Test
     fun testClearAllButtonVisible_whenHasNoClearableNotifs() =
-        testComponent.runTest {
-            val visible by collectLastValue(footerViewModel.clearAllButton.isVisible)
+        testScope.runTest {
+            val visible by collectLastValue(underTest.clearAllButton.isVisible)
 
             activeNotificationListRepository.notifStats.value =
                 NotifStats(
@@ -171,12 +123,12 @@
 
     @Test
     fun testClearAllButtonAnimating_whenShadeExpandedAndTouchable() =
-        testComponent.runTest {
-            val visible by collectLastValue(footerViewModel.clearAllButton.isVisible)
+        testScope.runTest {
+            val visible by collectLastValue(underTest.clearAllButton.isVisible)
             runCurrent()
 
             // WHEN shade is expanded
-            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
             shadeRepository.setLegacyShadeExpansion(1f)
             // AND QS not expanded
             shadeRepository.setQsExpansion(0f)
@@ -205,12 +157,12 @@
 
     @Test
     fun testClearAllButtonAnimating_whenShadeNotExpanded() =
-        testComponent.runTest {
-            val visible by collectLastValue(footerViewModel.clearAllButton.isVisible)
+        testScope.runTest {
+            val visible by collectLastValue(underTest.clearAllButton.isVisible)
             runCurrent()
 
             // WHEN shade is collapsed
-            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
             shadeRepository.setLegacyShadeExpansion(0f)
             // AND QS not expanded
             shadeRepository.setQsExpansion(0f)
@@ -236,4 +188,30 @@
             // THEN button visibility should not animate
             assertThat(visible?.isAnimating).isFalse()
         }
+
+    @Test
+    fun testManageButton_whenHistoryDisabled() =
+        testScope.runTest {
+            val buttonLabel by collectLastValue(underTest.manageOrHistoryButton.labelId)
+            runCurrent()
+
+            // WHEN notification history is disabled
+            fakeSecureSettingsRepository.setInt(Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0)
+
+            // THEN label is "Manage"
+            assertThat(buttonLabel).isEqualTo(R.string.manage_notifications_text)
+        }
+
+    @Test
+    fun testHistoryButton_whenHistoryEnabled() =
+        testScope.runTest {
+            val buttonLabel by collectLastValue(underTest.manageOrHistoryButton.labelId)
+            runCurrent()
+
+            // WHEN notification history is disabled
+            fakeSecureSettingsRepository.setInt(Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 1)
+
+            // THEN label is "History"
+            assertThat(buttonLabel).isEqualTo(R.string.manage_notifications_history_text)
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index dbe63f2..7589a49 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.notification.stack;
 
-import static com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING;
+import static com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING;
 import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index 4188c5d..88e4f5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -23,36 +23,27 @@
 import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.collectLastValue
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.statusbar.notification.dagger.NotificationStatsLoggerModule
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
 import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
-import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModelModule
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule
-import com.android.systemui.statusbar.policy.FakeConfigurationController
-import com.android.systemui.statusbar.policy.data.repository.FakeZenModeRepository
-import com.android.systemui.unfold.UnfoldTransitionModule
-import com.android.systemui.user.domain.interactor.HeadlessSystemUserModeModule
+import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
+import com.android.systemui.statusbar.policy.fakeConfigurationController
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
-import dagger.BindsInstance
-import dagger.Component
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -62,46 +53,18 @@
 @RunWith(AndroidJUnit4::class)
 @EnableFlags(FooterViewRefactor.FLAG_NAME)
 class NotificationListViewModelTest : SysuiTestCase() {
-
-    @SysUISingleton
-    @Component(
-        modules =
-            [
-                SysUITestModule::class,
-                ActivatableNotificationViewModelModule::class,
-                FooterViewModelModule::class,
-                HeadlessSystemUserModeModule::class,
-                UnfoldTransitionModule.Bindings::class,
-                NotificationStatsLoggerModule::class,
-            ]
-    )
-    interface TestComponent : SysUITestComponent<NotificationListViewModel> {
-        val activeNotificationListRepository: ActiveNotificationListRepository
-        val keyguardTransitionRepository: FakeKeyguardTransitionRepository
-        val shadeRepository: FakeShadeRepository
-        val zenModeRepository: FakeZenModeRepository
-        val configurationController: FakeConfigurationController
-
-        @Component.Factory
-        interface Factory {
-            fun create(
-                @BindsInstance test: SysuiTestCase,
-                featureFlags: FakeFeatureFlagsClassicModule,
-                mocks: TestMocksModule,
-            ): TestComponent
+    private val kosmos =
+        testKosmos().apply {
+            fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
         }
-    }
+    private val testScope = kosmos.testScope
+    private val activeNotificationListRepository = kosmos.activeNotificationListRepository
+    private val fakeKeyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val fakeShadeRepository = kosmos.fakeShadeRepository
+    private val zenModeRepository = kosmos.zenModeRepository
+    private val fakeConfigurationController = kosmos.fakeConfigurationController
 
-    private val testComponent: TestComponent =
-        DaggerNotificationListViewModelTest_TestComponent.factory()
-            .create(
-                test = this,
-                featureFlags =
-                    FakeFeatureFlagsClassicModule {
-                        set(com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER, true)
-                    },
-                mocks = TestMocksModule()
-            )
+    val underTest = kosmos.notificationListViewModel
 
     @Before
     fun setUp() {
@@ -110,11 +73,11 @@
 
     @Test
     fun testIsImportantForAccessibility_falseWhenNoNotifs() =
-        testComponent.runTest {
+        testScope.runTest {
             val important by collectLastValue(underTest.isImportantForAccessibility)
 
             // WHEN on lockscreen
-            keyguardTransitionRepository.sendTransitionSteps(
+            fakeKeyguardTransitionRepository.sendTransitionSteps(
                 from = KeyguardState.GONE,
                 to = KeyguardState.LOCKSCREEN,
                 testScope,
@@ -129,11 +92,11 @@
 
     @Test
     fun testIsImportantForAccessibility_trueWhenNotifs() =
-        testComponent.runTest {
+        testScope.runTest {
             val important by collectLastValue(underTest.isImportantForAccessibility)
 
             // WHEN on lockscreen
-            keyguardTransitionRepository.sendTransitionSteps(
+            fakeKeyguardTransitionRepository.sendTransitionSteps(
                 from = KeyguardState.GONE,
                 to = KeyguardState.LOCKSCREEN,
                 testScope,
@@ -148,11 +111,11 @@
 
     @Test
     fun testIsImportantForAccessibility_trueWhenNotKeyguard() =
-        testComponent.runTest {
+        testScope.runTest {
             val important by collectLastValue(underTest.isImportantForAccessibility)
 
             // WHEN not on lockscreen
-            keyguardTransitionRepository.sendTransitionSteps(
+            fakeKeyguardTransitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
                 to = KeyguardState.GONE,
                 testScope,
@@ -167,7 +130,7 @@
 
     @Test
     fun testShouldShowEmptyShadeView_trueWhenNoNotifs() =
-        testComponent.runTest {
+        testScope.runTest {
             val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
 
             // WHEN has no notifs
@@ -180,7 +143,7 @@
 
     @Test
     fun testShouldShowEmptyShadeView_falseWhenNotifs() =
-        testComponent.runTest {
+        testScope.runTest {
             val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
 
             // WHEN has notifs
@@ -193,13 +156,13 @@
 
     @Test
     fun testShouldShowEmptyShadeView_falseWhenQsExpandedDefault() =
-        testComponent.runTest {
+        testScope.runTest {
             val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
 
             // WHEN has no notifs
             activeNotificationListRepository.setActiveNotifs(count = 0)
             // AND quick settings are expanded
-            shadeRepository.legacyQsFullscreen.value = true
+            fakeShadeRepository.legacyQsFullscreen.value = true
             runCurrent()
 
             // THEN should not show
@@ -208,16 +171,16 @@
 
     @Test
     fun testShouldShowEmptyShadeView_trueWhenQsExpandedInSplitShade() =
-        testComponent.runTest {
+        testScope.runTest {
             val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
 
             // WHEN has no notifs
             activeNotificationListRepository.setActiveNotifs(count = 0)
             // AND quick settings are expanded
-            shadeRepository.setQsExpansion(1f)
+            fakeShadeRepository.setQsExpansion(1f)
             // AND split shade is enabled
             overrideResource(R.bool.config_use_split_notification_shade, true)
-            configurationController.notifyConfigurationChanged()
+            fakeConfigurationController.notifyConfigurationChanged()
             runCurrent()
 
             // THEN should show
@@ -226,13 +189,13 @@
 
     @Test
     fun testShouldShowEmptyShadeView_falseWhenTransitioningToAOD() =
-        testComponent.runTest {
+        testScope.runTest {
             val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
 
             // WHEN has no notifs
             activeNotificationListRepository.setActiveNotifs(count = 0)
             // AND transitioning to AOD
-            keyguardTransitionRepository.sendTransitionStep(
+            fakeKeyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
                     transitionState = TransitionState.STARTED,
                     from = KeyguardState.LOCKSCREEN,
@@ -248,13 +211,13 @@
 
     @Test
     fun testShouldShowEmptyShadeView_falseWhenBouncerShowing() =
-        testComponent.runTest {
+        testScope.runTest {
             val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
 
             // WHEN has no notifs
             activeNotificationListRepository.setActiveNotifs(count = 0)
             // AND is on bouncer
-            keyguardTransitionRepository.sendTransitionSteps(
+            fakeKeyguardTransitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
                 to = KeyguardState.PRIMARY_BOUNCER,
                 testScope,
@@ -267,7 +230,7 @@
 
     @Test
     fun testAreNotificationsHiddenInShade_true() =
-        testComponent.runTest {
+        testScope.runTest {
             val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
 
             zenModeRepository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST)
@@ -279,7 +242,7 @@
 
     @Test
     fun testAreNotificationsHiddenInShade_false() =
-        testComponent.runTest {
+        testScope.runTest {
             val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
 
             zenModeRepository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST)
@@ -291,7 +254,7 @@
 
     @Test
     fun testHasFilteredOutSeenNotifications_true() =
-        testComponent.runTest {
+        testScope.runTest {
             val hasFilteredNotifs by collectLastValue(underTest.hasFilteredOutSeenNotifications)
 
             activeNotificationListRepository.hasFilteredOutSeenNotifications.value = true
@@ -302,7 +265,7 @@
 
     @Test
     fun testHasFilteredOutSeenNotifications_false() =
-        testComponent.runTest {
+        testScope.runTest {
             val hasFilteredNotifs by collectLastValue(underTest.hasFilteredOutSeenNotifications)
 
             activeNotificationListRepository.hasFilteredOutSeenNotifications.value = false
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
index 9d53e7d..9919c6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
@@ -24,7 +24,7 @@
 import android.service.notification.StatusBarNotification
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
-import com.android.systemui.Flags
+import com.android.server.notification.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import org.junit.Assert.assertFalse
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryKosmos.kt
index 961022f..a4f28f3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryKosmos.kt
@@ -20,4 +20,4 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 
-val Kosmos.biometricStatusRepository by Fixture { FakeBiometricStatusRepository() }
+var Kosmos.biometricStatusRepository by Fixture { FakeBiometricStatusRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeBiometricStatusRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeBiometricStatusRepository.kt
index 1c8bd3b..e9b7a69 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeBiometricStatusRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeBiometricStatusRepository.kt
@@ -18,9 +18,12 @@
 package com.android.systemui.biometrics.data.repository
 
 import com.android.systemui.biometrics.shared.model.AuthenticationReason
+import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
 
 class FakeBiometricStatusRepository : BiometricStatusRepository {
     private val _fingerprintAuthenticationReason =
@@ -28,7 +31,16 @@
     override val fingerprintAuthenticationReason: StateFlow<AuthenticationReason> =
         _fingerprintAuthenticationReason.asStateFlow()
 
+    private val _fingerprintAcquiredStatus =
+        MutableStateFlow<FingerprintAuthenticationStatus?>(null)
+    override val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus> =
+        _fingerprintAcquiredStatus.asStateFlow().filterNotNull()
+
     fun setFingerprintAuthenticationReason(reason: AuthenticationReason) {
         _fingerprintAuthenticationReason.value = reason
     }
+
+    fun setFingerprintAcquiredStatus(status: FingerprintAuthenticationStatus) {
+        _fingerprintAcquiredStatus.value = status
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
index 3ea3ccf..1884a32 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
@@ -28,7 +28,7 @@
     fun mediaActive(timestamp: Long = 0L) {
         _mediaModel.value =
             CommunalMediaModel(
-                hasAnyMediaOrRecommendation = true,
+                hasActiveMediaOrRecommendation = true,
                 createdTimestampMillis = timestamp,
             )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryKosmos.kt
new file mode 100644
index 0000000..109e113
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 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.controls.panels
+
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.settings.fakeUserFileManager
+import com.android.systemui.settings.fakeUserTracker
+
+var Kosmos.authorizedPanelsRepository: AuthorizedPanelsRepository by
+    Kosmos.Fixture {
+        AuthorizedPanelsRepositoryImpl(applicationContext, fakeUserFileManager, fakeUserTracker)
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt
deleted file mode 100644
index a231212..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt
+++ /dev/null
@@ -1,74 +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.controls.panels
-
-import android.os.UserHandle
-import com.android.systemui.kosmos.Kosmos
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-
-class FakeSelectedComponentRepository : SelectedComponentRepository {
-    private var shouldAddDefaultPanel: Boolean = true
-    private val _selectedComponentFlows =
-        mutableMapOf<UserHandle, MutableStateFlow<SelectedComponentRepository.SelectedComponent?>>()
-    private var currentUserHandle: UserHandle = UserHandle.of(0)
-
-    override fun selectedComponentFlow(
-        userHandle: UserHandle
-    ): Flow<SelectedComponentRepository.SelectedComponent?> {
-        // Return an existing flow for the user or create a new one
-        return _selectedComponentFlows.getOrPut(getUserHandle(userHandle)) {
-            MutableStateFlow(null)
-        }
-    }
-
-    override fun getSelectedComponent(
-        userHandle: UserHandle
-    ): SelectedComponentRepository.SelectedComponent? {
-        return _selectedComponentFlows[getUserHandle(userHandle)]?.value
-    }
-
-    override fun setSelectedComponent(
-        selectedComponent: SelectedComponentRepository.SelectedComponent
-    ) {
-        val flow = _selectedComponentFlows.getOrPut(currentUserHandle) { MutableStateFlow(null) }
-        flow.value = selectedComponent
-    }
-
-    override fun removeSelectedComponent() {
-        _selectedComponentFlows[currentUserHandle]?.value = null
-    }
-    override fun shouldAddDefaultComponent(): Boolean = shouldAddDefaultPanel
-
-    override fun setShouldAddDefaultComponent(shouldAdd: Boolean) {
-        shouldAddDefaultPanel = shouldAdd
-    }
-
-    fun setCurrentUserHandle(userHandle: UserHandle) {
-        currentUserHandle = userHandle
-    }
-    private fun getUserHandle(userHandle: UserHandle): UserHandle {
-        return if (userHandle == UserHandle.CURRENT) {
-            currentUserHandle
-        } else {
-            userHandle
-        }
-    }
-}
-
-val Kosmos.selectedComponentRepository by
-    Kosmos.Fixture<FakeSelectedComponentRepository> { FakeSelectedComponentRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/SelectedComponentRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/SelectedComponentRepositoryKosmos.kt
new file mode 100644
index 0000000..ee5b7e5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/controls/panels/SelectedComponentRepositoryKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 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.controls.panels
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.settings.fakeUserFileManager
+import com.android.systemui.settings.fakeUserTracker
+
+var Kosmos.selectedComponentRepository: SelectedComponentRepository by
+    Kosmos.Fixture {
+        SelectedComponentRepositoryImpl(fakeUserFileManager, fakeUserTracker, testDispatcher)
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserFileManager.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserFileManager.kt
new file mode 100644
index 0000000..207c3f7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserFileManager.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 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.settings
+
+import android.content.SharedPreferences
+import com.android.systemui.util.FakeSharedPreferences
+import java.io.File
+
+class FakeUserFileManager : UserFileManager {
+    private val sharedPreferences = mutableMapOf<SharedPrefKey, FakeSharedPreferences>()
+
+    override fun getFile(fileName: String, userId: Int): File {
+        throw UnsupportedOperationException("getFile not implemented in fake")
+    }
+
+    override fun getSharedPreferences(fileName: String, mode: Int, userId: Int): SharedPreferences {
+        val key = SharedPrefKey(fileName, mode, userId)
+        return sharedPreferences.getOrPut(key) { FakeSharedPreferences() }
+    }
+
+    private data class SharedPrefKey(val fileName: String, val mode: Int, val userId: Int)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/UserFileManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/UserFileManagerKosmos.kt
new file mode 100644
index 0000000..4d7a40a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/UserFileManagerKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 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.settings
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeUserFileManager by Kosmos.Fixture { FakeUserFileManager() }
+var Kosmos.userFileManager: UserFileManager by Kosmos.Fixture { fakeUserFileManager }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
index e13fa52..82e0b8e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.plugins.statusbar.statusBarStateController
 import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -44,6 +45,7 @@
 val Kosmos.shadeControllerSceneImpl by
     Kosmos.Fixture {
         ShadeControllerSceneImpl(
+            mainDispatcher = testDispatcher,
             scope = applicationCoroutineScope,
             shadeInteractor = shadeInteractor,
             sceneInteractor = sceneInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt
index ff22ca0..01cac4c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt
@@ -19,12 +19,14 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shared.notifications.domain.interactor.notificationSettingsInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
 
 val Kosmos.footerViewModel by Fixture {
     FooterViewModel(
         activeNotificationsInteractor = activeNotificationsInteractor,
+        notificationSettingsInteractor = notificationSettingsInteractor,
         seenNotificationsInteractor = seenNotificationsInteractor,
         shadeInteractor = shadeInteractor,
     )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
index 748d04d..489598c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.notificationIconContainerShelfViewBinder
+import com.android.systemui.statusbar.notification.notificationActivityStarter
 import com.android.systemui.statusbar.notification.stack.ui.view.notificationStatsLogger
 import com.android.systemui.statusbar.notification.stack.displaySwitchNotificationsHiderTracker
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationListViewModel
@@ -36,9 +37,10 @@
         configuration = configurationState,
         falsingManager = falsingManager,
         iconAreaController = notificationIconAreaController,
+        loggerOptional = Optional.of(notificationStatsLogger),
         metricsLogger = metricsLogger,
         hiderTracker = displaySwitchNotificationsHiderTracker,
         nicBinder = notificationIconContainerShelfViewBinder,
-        loggerOptional = Optional.of(notificationStatsLogger),
+        notificationActivityStarter = { notificationActivityStarter },
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
index 33ed7e6..d4e9bfb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.statusbar.policy
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.util.mockito.mock
 
-val Kosmos.configurationController by Kosmos.Fixture { mock<ConfigurationController>() }
+var Kosmos.configurationController: ConfigurationController by
+    Kosmos.Fixture { fakeConfigurationController }
 val Kosmos.fakeConfigurationController: FakeConfigurationController by
     Kosmos.Fixture { FakeConfigurationController() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeDeviceProvisioningRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeDeviceProvisioningRepository.kt
index 3002299..fc6a800 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeDeviceProvisioningRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeDeviceProvisioningRepository.kt
@@ -24,7 +24,7 @@
 
 @SysUISingleton
 class FakeDeviceProvisioningRepository @Inject constructor() : DeviceProvisioningRepository {
-    private val _isDeviceProvisioned = MutableStateFlow(false)
+    private val _isDeviceProvisioned = MutableStateFlow(true)
     override val isDeviceProvisioned: Flow<Boolean> = _isDeviceProvisioned
     private val _isFactoryResetProtectionActive = MutableStateFlow(false)
     override val isFactoryResetProtectionActive: Flow<Boolean> = _isFactoryResetProtectionActive
diff --git a/ravenwood/bulk_enable.py b/ravenwood/bulk_enable.py
index 36d398c..83fda9e 100644
--- a/ravenwood/bulk_enable.py
+++ b/ravenwood/bulk_enable.py
@@ -34,6 +34,8 @@
 
 re_result = re.compile("I/ModuleListener.+?null-device-0 (.+?)#(.+?) ([A-Z_]+)(.*)$")
 
+DRY_RUN = "-n" in sys.argv
+
 ANNOTATION = "@android.platform.test.annotations.EnabledOnRavenwood"
 SED_ARG = "s/^((public )?class )/%s\\n\\1/g" % (ANNOTATION)
 
@@ -46,7 +48,7 @@
 stats_class = collections.defaultdict(lambda: collections.defaultdict(int))
 stats_method = collections.defaultdict()
 
-with open(sys.argv[1]) as f:
+with open(sys.argv[-1]) as f:
     for line in f.readlines():
         result = re_result.search(line)
         if result:
@@ -67,7 +69,7 @@
         clazz_match = re.compile("%s\.(kt|java)" % (clazz.split(".")[-1]))
         for root, dirs, files in os.walk("."):
             for f in files:
-                if clazz_match.match(f):
+                if clazz_match.match(f) and not DRY_RUN:
                     path = os.path.join(root, f)
                     subprocess.run(["sed", "-i", "-E", SED_ARG, path])
 
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index 3670459..8af561f 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -16,7 +16,9 @@
 
 package android.platform.test.ravenwood;
 
+import android.app.ActivityManager;
 import android.app.Instrumentation;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.HandlerThread;
 import android.os.Looper;
@@ -30,10 +32,12 @@
 
 import java.io.PrintStream;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
 
 public class RavenwoodRuleImpl {
     private static final String MAIN_THREAD_NAME = "RavenwoodMain";
@@ -50,11 +54,27 @@
 
     private static ScheduledFuture<?> sPendingTimeout;
 
+    /**
+     * When set, an unhandled exception was discovered (typically on a background thread), and we
+     * capture it here to ensure it's reported as a test failure.
+     */
+    private static final AtomicReference<Throwable> sPendingUncaughtException =
+            new AtomicReference<>();
+
+    private static final Thread.UncaughtExceptionHandler sUncaughtExceptionHandler =
+            (thread, throwable) -> {
+                // Remember the first exception we discover
+                sPendingUncaughtException.compareAndSet(null, throwable);
+            };
+
     public static boolean isOnRavenwood() {
         return true;
     }
 
     public static void init(RavenwoodRule rule) {
+        maybeThrowPendingUncaughtException(false);
+        Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler);
+
         RuntimeInit.redirectLogStreams();
 
         android.os.Process.init$ravenwood(rule.mUid, rule.mPid);
@@ -64,6 +84,8 @@
                 rule.mSystemProperties.getKeyReadablePredicate(),
                 rule.mSystemProperties.getKeyWritablePredicate());
 
+        ActivityManager.init$ravenwood(rule.mCurrentUser);
+
         com.android.server.LocalServices.removeAllServicesForTest();
 
         if (rule.mProvideMainThread) {
@@ -78,6 +100,10 @@
             sPendingTimeout = sTimeoutExecutor.schedule(RavenwoodRuleImpl::dumpStacks,
                     TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
         }
+
+        // Touch some references early to ensure they're <clinit>'ed
+        Objects.requireNonNull(Build.IS_USERDEBUG);
+        Objects.requireNonNull(Build.VERSION.SDK);
     }
 
     public static void reset(RavenwoodRule rule) {
@@ -94,9 +120,13 @@
 
         com.android.server.LocalServices.removeAllServicesForTest();
 
+        ActivityManager.reset$ravenwood();
+
         android.os.SystemProperties.reset$ravenwood();
         android.os.Binder.reset$ravenwood();
         android.os.Process.reset$ravenwood();
+
+        maybeThrowPendingUncaughtException(true);
     }
 
     public static void logTestRunner(String label, Description description) {
@@ -120,4 +150,21 @@
         }
         out.println("-----END ALL THREAD STACKS-----");
     }
+
+    /**
+     * If there's a pending uncaught exception, consume and throw it now. Typically used to
+     * report an exception on a background thread as a failure for the currently running test.
+     */
+    private static void maybeThrowPendingUncaughtException(boolean duringReset) {
+        final Throwable pending = sPendingUncaughtException.getAndSet(null);
+        if (pending != null) {
+            if (duringReset) {
+                throw new IllegalStateException(
+                        "Found an uncaught exception during this test", pending);
+            } else {
+                throw new IllegalStateException(
+                        "Found an uncaught exception before this test started", pending);
+            }
+        }
+    }
 }
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 0285b38..daed457 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -16,6 +16,10 @@
 
 package android.platform.test.ravenwood;
 
+import static android.os.Process.FIRST_APPLICATION_UID;
+import static android.os.Process.SYSTEM_UID;
+import static android.os.UserHandle.USER_SYSTEM;
+
 import static org.junit.Assert.fail;
 
 import android.platform.test.annotations.DisabledOnRavenwood;
@@ -94,12 +98,12 @@
         }
     }
 
-    private static final int SYSTEM_UID = 1000;
     private static final int NOBODY_UID = 9999;
-    private static final int FIRST_APPLICATION_UID = 10000;
 
     private static final AtomicInteger sNextPid = new AtomicInteger(100);
 
+    int mCurrentUser = USER_SYSTEM;
+
     /**
      * Unless the test author requests differently, run as "nobody", and give each collection of
      * tests its own unique PID.
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index e49b64e..a5ecd20 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -82,6 +82,8 @@
 android.os.UidBatteryConsumer$Builder
 android.os.UserHandle
 android.os.UserManager
+android.os.VibrationAttributes
+android.os.VibrationAttributes$Builder
 android.os.WorkSource
 
 android.content.ClipData
@@ -144,6 +146,7 @@
 
 android.content.ContentProvider
 
+android.app.ActivityManager
 android.app.Instrumentation
 
 android.metrics.LogMaker
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index cd8be33..82d9377 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server;
 
 import static android.app.Flags.modesApi;
+import static android.app.Flags.enableNightModeCache;
 import static android.app.UiModeManager.ContrastUtils.CONTRAST_DEFAULT_VALUE;
 import static android.app.UiModeManager.DEFAULT_PRIORITY;
 import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
@@ -135,7 +136,23 @@
     private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
 
     private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
-    private int mNightMode = UiModeManager.MODE_NIGHT_NO;
+
+    private final NightMode mNightMode = new NightMode(){
+        private int mNightModeValue = UiModeManager.MODE_NIGHT_NO;
+
+        @Override
+        public int get() {
+            return mNightModeValue;
+        }
+
+        @Override
+        public void set(int mode) {
+            mNightModeValue = mode;
+            if (enableNightModeCache()) {
+                UiModeManager.invalidateNightModeCache();
+            }
+        }
+    };
     private int mNightModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
     private int mAttentionModeThemeOverlay = UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
     private final LocalTime DEFAULT_CUSTOM_NIGHT_START_TIME = LocalTime.of(22, 0);
@@ -297,7 +314,7 @@
         @Override
         public void onTwilightStateChanged(@Nullable TwilightState state) {
             synchronized (mLock) {
-                if (mNightMode == UiModeManager.MODE_NIGHT_AUTO && mSystemReady) {
+                if (mNightMode.get() == UiModeManager.MODE_NIGHT_AUTO && mSystemReady) {
                     if (shouldApplyAutomaticChangesImmediately()) {
                         updateLocked(0, 0);
                     } else {
@@ -392,7 +409,7 @@
 
     private void updateSystemProperties() {
         int mode = Secure.getIntForUser(getContext().getContentResolver(), Secure.UI_NIGHT_MODE,
-                mNightMode, 0);
+                mNightMode.get(), 0);
         if (mode == MODE_NIGHT_AUTO || mode == MODE_NIGHT_CUSTOM) {
             mode = MODE_NIGHT_YES;
         }
@@ -412,7 +429,7 @@
     @Override
     public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
         mCurrentUser = to.getUserIdentifier();
-        if (mNightMode == MODE_NIGHT_AUTO) persistComputedNightMode(from.getUserIdentifier());
+        if (mNightMode.get() == MODE_NIGHT_AUTO) persistComputedNightMode(from.getUserIdentifier());
         getContext().getContentResolver().unregisterContentObserver(mSetupWizardObserver);
         verifySetupWizardCompleted();
         synchronized (mLock) {
@@ -471,12 +488,12 @@
         verifySetupWizardCompleted();
 
         final Resources res = context.getResources();
+        mNightMode.set(res.getInteger(
+                com.android.internal.R.integer.config_defaultNightMode));
         mStartDreamImmediatelyOnDock = res.getBoolean(
                 com.android.internal.R.bool.config_startDreamImmediatelyOnDock);
         mDreamsDisabledByAmbientModeSuppression = res.getBoolean(
                 com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig);
-        mNightMode = res.getInteger(
-                com.android.internal.R.integer.config_defaultNightMode);
         mDefaultUiModeType = res.getInteger(
                 com.android.internal.R.integer.config_defaultUiModeType);
         mCarModeKeepsScreenOn = (res.getInteger(
@@ -510,7 +527,7 @@
     private final BroadcastReceiver mOnShutdown = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (mNightMode == MODE_NIGHT_AUTO) {
+            if (mNightMode.get() == MODE_NIGHT_AUTO) {
                 persistComputedNightMode(mCurrentUser);
             }
         }
@@ -585,7 +602,7 @@
     }
 
     private void updateCustomTimeLocked() {
-        if (mNightMode != MODE_NIGHT_CUSTOM) return;
+        if (mNightMode.get() != MODE_NIGHT_CUSTOM) return;
         if (shouldApplyAutomaticChangesImmediately()) {
             updateLocked(0, 0);
         } else {
@@ -606,9 +623,9 @@
             return;
         }
         if (mSetupWizardComplete) {
-            mNightMode = Secure.getIntForUser(context.getContentResolver(),
+            mNightMode.set(Secure.getIntForUser(context.getContentResolver(),
                     Secure.UI_NIGHT_MODE, res.getInteger(
-                            com.android.internal.R.integer.config_defaultNightMode), userId);
+                            com.android.internal.R.integer.config_defaultNightMode), userId));
             mNightModeCustomType = Secure.getIntForUser(context.getContentResolver(),
                     Secure.UI_NIGHT_MODE_CUSTOM_TYPE, MODE_NIGHT_CUSTOM_TYPE_UNKNOWN, userId);
                     mOverrideNightModeOn = Secure.getIntForUser(context.getContentResolver(),
@@ -623,7 +640,7 @@
                     Secure.getLongForUser(context.getContentResolver(),
                             Secure.DARK_THEME_CUSTOM_END_TIME,
                             DEFAULT_CUSTOM_NIGHT_END_TIME.toNanoOfDay() / 1000L, userId) * 1000);
-            if (mNightMode == MODE_NIGHT_AUTO) {
+            if (mNightMode.get() == MODE_NIGHT_AUTO) {
                 mComputedNightMode = Secure.getIntForUser(context.getContentResolver(),
                         Secure.UI_NIGHT_MODE_LAST_COMPUTED, 0, userId) != 0;
             }
@@ -834,21 +851,23 @@
             final long ident = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
-                    if (mNightMode != mode || mNightModeCustomType != customModeType) {
-                        if (mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM) {
+                    if (mNightMode.get() != mode || mNightModeCustomType != customModeType) {
+                        if (mNightMode.get() == MODE_NIGHT_AUTO
+                                || mNightMode.get() == MODE_NIGHT_CUSTOM) {
                             unregisterDeviceInactiveListenerLocked();
                             cancelCustomAlarm();
                         }
                         mNightModeCustomType = mode == MODE_NIGHT_CUSTOM
                                 ? customModeType
                                 : MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
-                        mNightMode = mode;
+                        mNightMode.set(mode);
                         //deactivates AttentionMode if user toggles DarkTheme
                         mAttentionModeThemeOverlay = MODE_ATTENTION_THEME_OVERLAY_OFF;
                         resetNightModeOverrideLocked();
                         persistNightMode(user);
                         // on screen off will update configuration instead
-                        if ((mNightMode != MODE_NIGHT_AUTO && mNightMode != MODE_NIGHT_CUSTOM)
+                        if ((mNightMode.get() != MODE_NIGHT_AUTO
+                                && mNightMode.get() != MODE_NIGHT_CUSTOM)
                                 || shouldApplyAutomaticChangesImmediately()) {
                             unregisterDeviceInactiveListenerLocked();
                             updateLocked(0, 0);
@@ -865,7 +884,7 @@
         @Override
         public int getNightMode() {
             synchronized (mLock) {
-                return mNightMode;
+                return mNightMode.get();
             }
         }
 
@@ -999,18 +1018,19 @@
             synchronized (mLock) {
                 final long ident = Binder.clearCallingIdentity();
                 try {
-                    if (mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM) {
+                    if (mNightMode.get() == MODE_NIGHT_AUTO
+                            || mNightMode.get() == MODE_NIGHT_CUSTOM) {
                         unregisterDeviceInactiveListenerLocked();
                         mOverrideNightModeOff = !active;
                         mOverrideNightModeOn = active;
                         mOverrideNightModeUser = mCurrentUser;
                         persistNightModeOverrides(mCurrentUser);
-                    } else if (mNightMode == UiModeManager.MODE_NIGHT_NO
+                    } else if (mNightMode.get() == UiModeManager.MODE_NIGHT_NO
                             && active) {
-                        mNightMode = UiModeManager.MODE_NIGHT_YES;
-                    } else if (mNightMode == UiModeManager.MODE_NIGHT_YES
+                        mNightMode.set(UiModeManager.MODE_NIGHT_YES);
+                    } else if (mNightMode.get() == UiModeManager.MODE_NIGHT_YES
                             && !active) {
-                        mNightMode = UiModeManager.MODE_NIGHT_NO;
+                        mNightMode.set(UiModeManager.MODE_NIGHT_NO);
                     }
                     updateConfigurationLocked();
                     applyConfigurationExternallyLocked();
@@ -1414,7 +1434,7 @@
 
     private void onCustomTimeUpdated(int user) {
         persistNightMode(user);
-        if (mNightMode != MODE_NIGHT_CUSTOM) return;
+        if (mNightMode.get() != MODE_NIGHT_CUSTOM) return;
         if (shouldApplyAutomaticChangesImmediately()) {
             unregisterDeviceInactiveListenerLocked();
             updateLocked(0, 0);
@@ -1431,8 +1451,8 @@
 
             pw.print(" mStartDreamImmediatelyOnDock="); pw.print(mStartDreamImmediatelyOnDock);
 
-            pw.print("  mNightMode="); pw.print(mNightMode); pw.print(" (");
-            pw.print(Shell.nightModeToStr(mNightMode, mNightModeCustomType)); pw.print(") ");
+            pw.print("  mNightMode="); pw.print(mNightMode.get()); pw.print(" (");
+            pw.print(Shell.nightModeToStr(mNightMode.get(), mNightModeCustomType)); pw.print(") ");
             pw.print(" mOverrideOn/Off="); pw.print(mOverrideNightModeOn);
             pw.print("/"); pw.print(mOverrideNightModeOff);
             pw.print("  mAttentionModeThemeOverlay="); pw.print(mAttentionModeThemeOverlay);
@@ -1623,7 +1643,7 @@
         // Only persist setting if not in car mode
         if (mCarModeEnabled || mCar) return;
         Secure.putIntForUser(getContext().getContentResolver(),
-                Secure.UI_NIGHT_MODE, mNightMode, user);
+                Secure.UI_NIGHT_MODE, mNightMode.get(), user);
         Secure.putLongForUser(getContext().getContentResolver(),
                 Secure.UI_NIGHT_MODE_CUSTOM_TYPE, mNightModeCustomType, user);
         Secure.putLongForUser(getContext().getContentResolver(),
@@ -1659,11 +1679,11 @@
             uiMode = Configuration.UI_MODE_TYPE_VR_HEADSET;
         }
 
-        if (mNightMode == MODE_NIGHT_YES || mNightMode == UiModeManager.MODE_NIGHT_NO) {
-            updateComputedNightModeLocked(mNightMode == MODE_NIGHT_YES);
+        if (mNightMode.get() == MODE_NIGHT_YES || mNightMode.get() == UiModeManager.MODE_NIGHT_NO) {
+            updateComputedNightModeLocked(mNightMode.get() == MODE_NIGHT_YES);
         }
 
-        if (mNightMode == MODE_NIGHT_AUTO) {
+        if (mNightMode.get() == MODE_NIGHT_AUTO) {
             boolean activateNightMode = mComputedNightMode;
             if (mTwilightManager != null) {
                 mTwilightManager.registerListener(mTwilightListener, mHandler);
@@ -1677,7 +1697,7 @@
             }
         }
 
-        if (mNightMode == MODE_NIGHT_CUSTOM) {
+        if (mNightMode.get() == MODE_NIGHT_CUSTOM) {
             if (mNightModeCustomType == MODE_NIGHT_CUSTOM_TYPE_BEDTIME) {
                 updateComputedNightModeLocked(mLastBedtimeRequestedNightMode);
             } else {
@@ -2010,7 +2030,7 @@
 
     private void updateComputedNightModeLocked(boolean activate) {
         boolean newComputedValue = activate;
-        if (mNightMode != MODE_NIGHT_YES && mNightMode != UiModeManager.MODE_NIGHT_NO) {
+        if (mNightMode.get() != MODE_NIGHT_YES && mNightMode.get() != UiModeManager.MODE_NIGHT_NO) {
             if (mOverrideNightModeOn && !newComputedValue) {
                 newComputedValue = true;
             } else if (mOverrideNightModeOff && newComputedValue) {
@@ -2029,7 +2049,7 @@
             mComputedNightMode = newComputedValue;
         }
 
-        if (mNightMode != MODE_NIGHT_AUTO || (mTwilightManager != null
+        if (mNightMode.get() != MODE_NIGHT_AUTO || (mTwilightManager != null
                 && mTwilightManager.getLastTwilightState() != null)) {
             resetNightModeOverrideLocked();
         }
@@ -2279,4 +2299,13 @@
             Sandman.startDreamWhenDockedIfAppropriate(context);
         }
     }
+
+    /**
+     * Interface to contain the value for system night mode. We make the night mode accessible
+     * through this class to ensure that the reassignment of this value invalidates the cache.
+     */
+    private interface NightMode {
+        int get();
+        void set(int mode);
+    }
 }
diff --git a/services/core/java/com/android/server/am/AssistDataRequester.java b/services/core/java/com/android/server/am/AssistDataRequester.java
index 98129ed..856a15f 100644
--- a/services/core/java/com/android/server/am/AssistDataRequester.java
+++ b/services/core/java/com/android/server/am/AssistDataRequester.java
@@ -222,7 +222,7 @@
         // Ensure that the current activity supports assist data
         boolean isAssistDataAllowed = false;
         try {
-            isAssistDataAllowed = mActivityTaskManager.isAssistDataAllowedOnCurrentActivity();
+            isAssistDataAllowed = mActivityTaskManager.isAssistDataAllowed();
         } catch (RemoteException e) {
             // Should never happen
         }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index cd295b5..f1eea72 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -2820,6 +2820,10 @@
         return mDeviceInventory.isBluetoothAudioDeviceCategoryFixed(address);
     }
 
+    /*package*/ boolean isSADevice(AdiDeviceState deviceState) {
+        return mAudioService.isSADevice(deviceState);
+    }
+
     //------------------------------------------------
     // for testing purposes only
     void clearDeviceInventory() {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 690c37a..102a960 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -307,9 +307,12 @@
                             && di.mPeerDeviceAddress.equals(ads2.getDeviceAddress()))) {
                         continue;
                     }
-                    ads2.setHasHeadTracker(updatedDevice.hasHeadTracker());
-                    ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
-                    ads2.setSAEnabled(updatedDevice.isSAEnabled());
+                    if (mDeviceBroker.isSADevice(updatedDevice)
+                            == mDeviceBroker.isSADevice(ads2)) {
+                        ads2.setHasHeadTracker(updatedDevice.hasHeadTracker());
+                        ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
+                        ads2.setSAEnabled(updatedDevice.isSAEnabled());
+                    }
                     ads2.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
 
                     mDeviceBroker.postUpdatedAdiDeviceState(ads2);
@@ -325,9 +328,12 @@
                             && di.mDeviceAddress.equals(ads2.getDeviceAddress()))) {
                         continue;
                     }
-                    ads2.setHasHeadTracker(updatedDevice.hasHeadTracker());
-                    ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
-                    ads2.setSAEnabled(updatedDevice.isSAEnabled());
+                    if (mDeviceBroker.isSADevice(updatedDevice)
+                            == mDeviceBroker.isSADevice(ads2)) {
+                        ads2.setHasHeadTracker(updatedDevice.hasHeadTracker());
+                        ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
+                        ads2.setSAEnabled(updatedDevice.isSAEnabled());
+                    }
                     ads2.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
 
                     mDeviceBroker.postUpdatedAdiDeviceState(ads2);
@@ -348,10 +354,11 @@
                     || !updatedDevice.getDeviceAddress().equals(ads.getDeviceAddress())) {
                 continue;
             }
-
-            ads.setHasHeadTracker(updatedDevice.hasHeadTracker());
-            ads.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
-            ads.setSAEnabled(updatedDevice.isSAEnabled());
+            if (mDeviceBroker.isSADevice(updatedDevice) == mDeviceBroker.isSADevice(ads)) {
+                ads.setHasHeadTracker(updatedDevice.hasHeadTracker());
+                ads.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
+                ads.setSAEnabled(updatedDevice.isSAEnabled());
+            }
             ads.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
 
             mDeviceBroker.postUpdatedAdiDeviceState(ads);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 9f7c07e..bbbba26 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -7855,7 +7855,7 @@
 
         sDeviceLogger.enqueue(new EventLogger.StringEvent("BlutoothActiveDeviceChanged for "
                 + BluetoothProfile.getProfileName(profile) + ", device update " + previousDevice
-                + " -> " + newDevice));
+                + " -> " + newDevice).printLog(TAG));
         AudioDeviceBroker.BtDeviceChangedData data =
                 new AudioDeviceBroker.BtDeviceChangedData(newDevice, previousDevice, info,
                         "AudioService");
@@ -10638,6 +10638,10 @@
         mSpatializerHelper.setFeatureEnabled(mHasSpatializerEffect);
     }
 
+    /*package*/ boolean isSADevice(AdiDeviceState deviceState) {
+        return mSpatializerHelper.isSADevice(deviceState);
+    }
+
     private boolean isBluetoothPrividged() {
         if (!bluetoothMacAddressAnonymization()) {
             return true;
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 8428f12..8d76731 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -813,7 +813,7 @@
         return false;
     }
 
-    private boolean isSADevice(AdiDeviceState deviceState) {
+    /*package*/ boolean isSADevice(AdiDeviceState deviceState) {
         return deviceState.getDeviceType() == getCanonicalDeviceType(deviceState.getDeviceType(),
                 deviceState.getInternalDeviceType()) && isDeviceCompatibleWithSpatializationModes(
                 deviceState.getAudioDeviceAttributes());
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java
index 1ae4d64..1dc882e5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java
@@ -18,6 +18,8 @@
 
 import android.annotation.NonNull;
 import android.hardware.biometrics.AuthenticationStateListener;
+import android.hardware.biometrics.BiometricFingerprintConstants;
+import android.hardware.biometrics.BiometricSourceType;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -115,7 +117,7 @@
      * @param userId The user Id for the requested authentication
      */
     public void onAuthenticationFailed(int requestReason, int userId) {
-        for (AuthenticationStateListener listener: mAuthenticationStateListeners) {
+        for (AuthenticationStateListener listener : mAuthenticationStateListeners) {
             try {
                 listener.onAuthenticationFailed(requestReason, userId);
             } catch (RemoteException e) {
@@ -125,6 +127,27 @@
         }
     }
 
+    /**
+     * Defines behavior in response to biometric being acquired.
+     * @param biometricSourceType identifies [BiometricSourceType] biometric was acquired for
+     * @param requestReason reason from [BiometricRequestConstants.RequestReason] for authentication
+     * @param acquiredInfo [BiometricFingerprintConstants.FingerprintAcquired] int corresponding to
+     *                     a known acquired message.
+     */
+    public void onAuthenticationAcquired(
+            BiometricSourceType biometricSourceType, int requestReason,
+            @BiometricFingerprintConstants.FingerprintAcquired int acquiredInfo
+    ) {
+        for (AuthenticationStateListener listener: mAuthenticationStateListeners) {
+            try {
+                listener.onAuthenticationAcquired(biometricSourceType, requestReason, acquiredInfo);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception in notifying listener that authentication "
+                        + "stopped", e);
+            }
+        }
+    }
+
     @Override
     public void binderDied() {
         // Do nothing, handled below
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index e0fd44b..8121a63 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -29,6 +29,7 @@
 import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
 import android.hardware.biometrics.BiometricManager.Authenticators;
+import android.hardware.biometrics.BiometricSourceType;
 import android.hardware.biometrics.common.ICancellationSignal;
 import android.hardware.biometrics.common.OperationState;
 import android.hardware.biometrics.fingerprint.PointerContext;
@@ -102,6 +103,7 @@
     private Runnable mAuthSuccessRunnable;
     private final Clock mClock;
 
+
     public FingerprintAuthenticationClient(
             @NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon,
@@ -280,6 +282,8 @@
     public void onAcquired(@FingerprintAcquired int acquiredInfo, int vendorCode) {
         // For UDFPS, notify SysUI with acquiredInfo, so that the illumination can be turned off
         // for most ACQUIRED messages. See BiometricFingerprintConstants#FingerprintAcquired
+        mAuthenticationStateListeners.onAuthenticationAcquired(
+                BiometricSourceType.FINGERPRINT, getRequestReason(), acquiredInfo);
         mSensorOverlays.ifUdfps(controller -> controller.onAcquired(getSensorId(), acquiredInfo));
         super.onAcquired(acquiredInfo, vendorCode);
         PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId());
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 60c532c..b6311af 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -28,6 +28,7 @@
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.biometrics.BiometricManager.Authenticators;
+import android.hardware.biometrics.BiometricSourceType;
 import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
@@ -201,6 +202,8 @@
 
     @Override
     public void onAcquired(int acquiredInfo, int vendorCode) {
+        mAuthenticationStateListeners.onAuthenticationAcquired(
+                BiometricSourceType.FINGERPRINT, getRequestReason(), acquiredInfo);
         super.onAcquired(acquiredInfo, vendorCode);
 
         @LockoutTracker.LockoutMode final int lockoutMode =
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
index a15cb10..a23c08a 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
@@ -716,7 +716,10 @@
             // Programmed
             int programedInfo = params[offset] & 0x0F;
             if (isValidProgrammedInfo(programedInfo)) {
-                if (programedInfo == 0x09 || programedInfo == 0x0B) {
+                offset = offset + 1;
+                // Duration Available (2 bytes)
+                if ((programedInfo == 0x09 || programedInfo == 0x0B)
+                        && params.length - offset >= 2) {
                     durationAvailable = true;
                 } else {
                     return true;
@@ -726,16 +729,17 @@
             // Non programmed
             int nonProgramedErrorInfo = params[offset] & 0x0F;
             if (isValidNotProgrammedErrorInfo(nonProgramedErrorInfo)) {
-                if (nonProgramedErrorInfo == 0x0E) {
+                offset = offset + 1;
+                // Duration Available (2 bytes)
+                if (nonProgramedErrorInfo == 0x0E && params.length - offset >= 2) {
                     durationAvailable = true;
                 } else {
                     return true;
                 }
             }
         }
-        offset = offset + 1;
         // Duration Available (2 bytes)
-        if (durationAvailable && params.length - offset >= 2) {
+        if (durationAvailable) {
             return (isValidDurationHours(params[offset]) && isValidMinute(params[offset + 1]));
         }
         return false;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 0f2af31..fff4939 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3708,6 +3708,30 @@
                         return InputBindResult.INVALID_USER;
                     }
 
+                    // Ensure that caller's focused window and display parameters are allowd to
+                    // display input method.
+                    final int imeClientFocus = mWindowManagerInternal.hasInputMethodClientFocus(
+                            windowToken, cs.mUid, cs.mPid, cs.mSelfReportedDisplayId);
+                    switch (imeClientFocus) {
+                        case WindowManagerInternal.ImeClientFocusResult.DISPLAY_ID_MISMATCH:
+                            Slog.e(TAG,
+                                    "startInputOrWindowGainedFocusInternal: display ID mismatch.");
+                            return InputBindResult.DISPLAY_ID_MISMATCH;
+                        case WindowManagerInternal.ImeClientFocusResult.NOT_IME_TARGET_WINDOW:
+                            // Check with the window manager to make sure this client actually
+                            // has a window with focus.  If not, reject.  This is thread safe
+                            // because if the focus changes some time before or after, the
+                            // next client receiving focus that has any interest in input will
+                            // be calling through here after that change happens.
+                            if (DEBUG) {
+                                Slog.w(TAG, "Focus gain on non-focused client " + cs.mClient
+                                        + " (uid=" + cs.mUid + " pid=" + cs.mPid + ")");
+                            }
+                            return InputBindResult.NOT_IME_TARGET_WINDOW;
+                        case WindowManagerInternal.ImeClientFocusResult.INVALID_DISPLAY_ID:
+                            return InputBindResult.INVALID_DISPLAY_ID;
+                    }
+
                     result = startInputOrWindowGainedFocusInternalLocked(startInputReason,
                             client, windowToken, startInputFlags, softInputMode, windowFlags,
                             editorInfo, inputConnection, remoteAccessibilityInputConnection,
@@ -3756,26 +3780,6 @@
                     + " imeDispatcher=" + imeDispatcher
                     + " cs=" + cs);
         }
-        final int imeClientFocus = mWindowManagerInternal.hasInputMethodClientFocus(
-                windowToken, cs.mUid, cs.mPid, cs.mSelfReportedDisplayId);
-        switch (imeClientFocus) {
-            case WindowManagerInternal.ImeClientFocusResult.DISPLAY_ID_MISMATCH:
-                Slog.e(TAG, "startInputOrWindowGainedFocusInternal: display ID mismatch.");
-                return InputBindResult.DISPLAY_ID_MISMATCH;
-            case WindowManagerInternal.ImeClientFocusResult.NOT_IME_TARGET_WINDOW:
-                // Check with the window manager to make sure this client actually
-                // has a window with focus.  If not, reject.  This is thread safe
-                // because if the focus changes some time before or after, the
-                // next client receiving focus that has any interest in input will
-                // be calling through here after that change happens.
-                if (DEBUG) {
-                    Slog.w(TAG, "Focus gain on non-focused client " + cs.mClient
-                            + " (uid=" + cs.mUid + " pid=" + cs.mPid + ")");
-                }
-                return InputBindResult.NOT_IME_TARGET_WINDOW;
-            case WindowManagerInternal.ImeClientFocusResult.INVALID_DISPLAY_ID:
-                return InputBindResult.INVALID_DISPLAY_ID;
-        }
 
         final boolean shouldClearFlag = mImePlatformCompatUtils.shouldClearShowForcedFlag(cs.mUid);
         // In case mShowForced flag affects the next client to keep IME visible, when the current
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 4da2cc9..1c9bbab 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2049,7 +2049,9 @@
                 if (!mUserProfiles.isProfileUser(userId)) {
                     mConditionProviders.onUserUnlocked(userId);
                     mListeners.onUserUnlocked(userId);
-                    mZenModeHelper.onUserUnlocked(userId);
+                    if (!android.app.Flags.modesApi()) {
+                        mZenModeHelper.onUserUnlocked(userId);
+                    }
                 }
             }
         }
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 41ff415..2f20bbe 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -328,6 +328,7 @@
         }
     }
 
+    // TODO: b/310620812 - Remove when MODES_API is inlined (no more callers).
     public void onUserUnlocked(int user) {
         loadConfigForUser(user, "onUserUnlocked");
     }
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index dbff442..722654a 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -43,6 +43,13 @@
 }
 
 flag {
+    name: "screenshare_notification_hiding"
+    namespace: "systemui"
+    description: "Enable hiding of notifications during screenshare"
+    bug: "312784809"
+}
+
+flag {
   name: "sensitive_notification_app_protection"
   namespace: "systemui"
   description: "This flag controls the sensitive notification app protections while screen sharing"
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 51790b8..b947aa3 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -5016,6 +5016,8 @@
             case KeyEvent.KEYCODE_STYLUS_BUTTON_SECONDARY:
             case KeyEvent.KEYCODE_STYLUS_BUTTON_TERTIARY:
             case KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL: {
+                Slog.i(TAG, "Stylus buttons event: " + keyCode + " received. Should handle event? "
+                        + mStylusButtonsEnabled);
                 if (mStylusButtonsEnabled) {
                     sendSystemKeyToStatusBarAsync(event);
                 }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index 8549957..37f3825 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -347,7 +347,7 @@
         for (Rect crop : relativeCropHints) {
             Rect originalRect = new Rect(crop);
             originalRect.scale(wallpaper.mSampleSize);
-            originalRect.offset(wallpaper.cropHint.left, wallpaper.cropHint.right);
+            originalRect.offset(wallpaper.cropHint.left, wallpaper.cropHint.top);
             result.add(originalRect);
         }
         return result;
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 1128d0c..2e0546e 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -1319,33 +1319,9 @@
         try {
             synchronized (mGlobalLock) {
                 final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
-                if (r == null) return;
-                final TransitionController controller = r.mTransitionController;
-                if (!controller.isShellTransitionsEnabled()) {
+                if (r != null) {
                     r.setShowWhenLocked(showWhenLocked);
-                    return;
                 }
-                if (controller.isCollecting()
-                        && !mService.mKeyguardController.isKeyguardLocked(r.getDisplayId())) {
-                    // Keyguard isn't locked, so this can be done as part of the collecting
-                    // transition.
-                    r.setShowWhenLocked(showWhenLocked);
-                    return;
-                }
-                final Transition transition = new Transition(
-                        showWhenLocked ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK,
-                        0 /* flags */, controller, mService.mWindowManager.mSyncEngine);
-                r.mTransitionController.startCollectOrQueue(transition, (deferred) -> {
-                    transition.collect(r);
-                    r.setShowWhenLocked(showWhenLocked);
-                    if (transition.isNoOp()) {
-                        transition.abort();
-                        return;
-                    }
-                    controller.requestStartTransition(transition, null /* trigger */,
-                            null /* remoteTransition */, null /* displayChange */);
-                    transition.setReady(r, true);
-                });
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -1358,34 +1334,9 @@
         try {
             synchronized (mGlobalLock) {
                 final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
-                if (r == null) return;
-                final TransitionController controller = r.mTransitionController;
-                // If shell transitions is not enabled just set it directly.
-                if (!controller.isShellTransitionsEnabled()) {
+                if (r != null) {
                     r.setInheritShowWhenLocked(inheritShowWhenLocked);
-                    return;
                 }
-                if (controller.isCollecting()
-                        && !mService.mKeyguardController.isKeyguardLocked(r.getDisplayId())) {
-                    // Keyguard isn't locked, so this can be done as part of the collecting
-                    // transition.
-                    r.setInheritShowWhenLocked(inheritShowWhenLocked);
-                    return;
-                }
-                final Transition transition = new Transition(
-                        inheritShowWhenLocked ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK,
-                        0 /* flags */, controller, mService.mWindowManager.mSyncEngine);
-                r.mTransitionController.startCollectOrQueue(transition, (deferred) -> {
-                    transition.collect(r);
-                    r.setInheritShowWhenLocked(inheritShowWhenLocked);
-                    if (transition.isNoOp()) {
-                        transition.abort();
-                        return;
-                    }
-                    controller.requestStartTransition(transition, null /* trigger */,
-                            null /* remoteTransition */, null /* displayChange */);
-                    transition.setReady(r, true);
-                });
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 3397a3d..0def5a1 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3505,8 +3505,9 @@
     }
 
     @Override
-    public boolean isAssistDataAllowedOnCurrentActivity() {
+    public boolean isAssistDataAllowed() {
         int userId;
+        boolean hasRestrictedWindow;
         synchronized (mGlobalLock) {
             final Task focusedRootTask = getTopDisplayFocusedRootTask();
             if (focusedRootTask == null || focusedRootTask.isActivityTypeAssistant()) {
@@ -3518,8 +3519,17 @@
                 return false;
             }
             userId = activity.mUserId;
+            DisplayContent displayContent = activity.getDisplayContent();
+            if (displayContent == null) {
+                return false;
+            }
+            hasRestrictedWindow = displayContent.forAllWindows(windowState -> {
+                return windowState.isOnScreen() && UserManager.isUserTypePrivateProfile(
+                        getUserManager().getProfileType(windowState.mShowUserId));
+            }, true /* traverseTopToBottom */);
         }
-        return DevicePolicyCache.getInstance().isScreenCaptureAllowed(userId);
+        return DevicePolicyCache.getInstance().isScreenCaptureAllowed(userId)
+                && !hasRestrictedWindow;
     }
 
     private void onLocalVoiceInteractionStartedLocked(IBinder activity,
diff --git a/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java b/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java
index eae9951..d08f043 100644
--- a/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java
+++ b/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.internal.perfetto.protos.PerfettoTrace;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.tracing.perfetto.DataSourceParams;
 import android.tracing.perfetto.InitArguments;
 import android.tracing.perfetto.Producer;
@@ -57,6 +58,16 @@
             return;
         }
 
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logSentTransition");
+        try {
+            doLogSentTransition(transition, targets);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+        }
+    }
+
+    private void doLogSentTransition(
+            Transition transition, ArrayList<Transition.ChangeInfo> targets) {
         mDataSource.trace((ctx) -> {
             final ProtoOutputStream os = ctx.newTracePacket();
 
@@ -91,6 +102,15 @@
             return;
         }
 
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logFinishedTransition");
+        try {
+            doLogFinishTransition(transition);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+        }
+    }
+
+    private void doLogFinishTransition(Transition transition) {
         mDataSource.trace((ctx) -> {
             final ProtoOutputStream os = ctx.newTracePacket();
 
@@ -114,6 +134,15 @@
             return;
         }
 
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logAbortedTransition");
+        try {
+            doLogAbortedTransition(transition);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+        }
+    }
+
+    private void doLogAbortedTransition(Transition transition) {
         mDataSource.trace((ctx) -> {
             final ProtoOutputStream os = ctx.newTracePacket();
 
@@ -131,6 +160,15 @@
             return;
         }
 
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logRemovingStartingWindow");
+        try {
+            doLogRemovingStartingWindow(startingData);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+        }
+    }
+
+    public void doLogRemovingStartingWindow(@NonNull StartingData startingData) {
         mDataSource.trace((ctx) -> {
             final ProtoOutputStream os = ctx.newTracePacket();
 
diff --git a/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
index bdb4588..967f415 100644
--- a/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
+++ b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
@@ -36,8 +36,7 @@
 import com.android.internal.protolog.common.ProtoLog;
 
 import java.io.PrintWriter;
-import java.util.Map;
-import java.util.Set;
+import java.util.ArrayList;
 
 public class ScreenRecordingCallbackController {
 
@@ -57,10 +56,10 @@
     }
 
     @GuardedBy("WindowManagerService.mGlobalLock")
-    private final Map<IBinder, Callback> mCallbacks = new ArrayMap<>();
+    private final ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<>();
 
     @GuardedBy("WindowManagerService.mGlobalLock")
-    private final Map<Integer /*UID*/, Boolean> mLastInvokedStateByUid = new ArrayMap<>();
+    private final ArrayMap<Integer /*UID*/, Boolean> mLastInvokedStateByUid = new ArrayMap<>();
 
     private final WindowManagerService mWms;
 
@@ -108,8 +107,8 @@
         }
 
         IBinder binder = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
-        IMediaProjectionManager mediaProjectionManager =
-                IMediaProjectionManager.Stub.asInterface(binder);
+        IMediaProjectionManager mediaProjectionManager = IMediaProjectionManager.Stub.asInterface(
+                binder);
 
         long identityToken = Binder.clearCallingIdentity();
         MediaProjectionInfo mediaProjectionInfo = null;
@@ -157,11 +156,14 @@
         synchronized (mWms.mGlobalLock) {
             IBinder binder = callback.asBinder();
             Callback callbackInfo = mCallbacks.remove(binder);
+            if (callbackInfo == null) {
+                return;
+            }
             binder.unlinkToDeath(callbackInfo, 0);
 
             boolean uidHasCallback = false;
-            for (Callback cb : mCallbacks.values()) {
-                if (cb.mUid == callbackInfo.mUid) {
+            for (int i = 0; i < mCallbacks.size(); i++) {
+                if (mCallbacks.valueAt(i).mUid == callbackInfo.mUid) {
                     uidHasCallback = true;
                     break;
                 }
@@ -213,7 +215,9 @@
             return;
         }
 
-        dispatchCallbacks(Set.of(uid), processVisible);
+        ArraySet<Integer> uidSet = new ArraySet<>();
+        uidSet.add(uid);
+        dispatchCallbacks(uidSet, processVisible);
     }
 
     @GuardedBy("WindowManagerService.mGlobalLock")
@@ -233,8 +237,8 @@
     }
 
     @GuardedBy("WindowManagerService.mGlobalLock")
-    private Set<Integer> getRecordedUids() {
-        Set<Integer> result = new ArraySet<>();
+    private ArraySet<Integer> getRecordedUids() {
+        ArraySet<Integer> result = new ArraySet<>();
         if (mRecordedWC == null) {
             return result;
         }
@@ -248,37 +252,43 @@
     }
 
     @GuardedBy("WindowManagerService.mGlobalLock")
-    private void dispatchCallbacks(Set<Integer> uids, boolean visibleInScreenRecording) {
+    private void dispatchCallbacks(ArraySet<Integer> uids, boolean visibleInScreenRecording) {
         if (uids.isEmpty()) {
             return;
         }
 
-        for (Integer uid : uids) {
-            mLastInvokedStateByUid.put(uid, visibleInScreenRecording);
+        for (int i = 0; i < uids.size(); i++) {
+            mLastInvokedStateByUid.put(uids.valueAt(i), visibleInScreenRecording);
         }
 
-        for (Callback callback : mCallbacks.values()) {
-            if (!uids.contains(callback.mUid)) {
-                continue;
-            }
-            try {
-                callback.mCallback.onScreenRecordingStateChanged(visibleInScreenRecording);
-            } catch (RemoteException e) {
-                // Client has died. Cleanup is handled via DeathRecipient.
+        ArrayList<IScreenRecordingCallback> callbacks = new ArrayList<>();
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            if (uids.contains(mCallbacks.valueAt(i).mUid)) {
+                callbacks.add(mCallbacks.valueAt(i).mCallback);
             }
         }
+
+        mWms.mH.post(() -> {
+            for (int i = 0; i < callbacks.size(); i++) {
+                try {
+                    callbacks.get(i).onScreenRecordingStateChanged(visibleInScreenRecording);
+                } catch (RemoteException e) {
+                    // Client has died. Cleanup is handled via DeathRecipient.
+                }
+            }
+        });
     }
 
     void dump(PrintWriter pw) {
         pw.format("ScreenRecordingCallbackController:\n");
         pw.format("  Registered callbacks:\n");
-        for (Map.Entry<IBinder, Callback> entry : mCallbacks.entrySet()) {
-            pw.format("    callback=%s uid=%s\n", entry.getKey(), entry.getValue().mUid);
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            pw.format("    callback=%s uid=%s\n", mCallbacks.keyAt(i), mCallbacks.valueAt(i).mUid);
         }
         pw.format("  Last invoked states:\n");
-        for (Map.Entry<Integer, Boolean> entry : mLastInvokedStateByUid.entrySet()) {
-            pw.format("    uid=%s isVisibleInScreenRecording=%s\n", entry.getKey(),
-                    entry.getValue());
+        for (int i = 0; i < mLastInvokedStateByUid.size(); i++) {
+            pw.format("    uid=%s isVisibleInScreenRecording=%s\n", mLastInvokedStateByUid.keyAt(i),
+                    mLastInvokedStateByUid.valueAt(i));
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 1e58306..2accf9a 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -2917,26 +2917,6 @@
         Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_WINDOW_MANAGER, TAG, cookie);
     }
 
-    /**
-     * Get whether the transition, in its current state, is a no-op. This should be avoided. It is
-     * only here for legacy usages where we can't tell ahead-of-time whether something will
-     * generate a change.
-     */
-    boolean isNoOp() {
-        for (int i = mParticipants.size() - 1; i >= 0; --i) {
-            // This is the same criteria as the rejection logic in calculateTargets
-            final WindowContainer<?> wc = mParticipants.valueAt(i);
-            if (!wc.isAttached()) continue;
-            // The level of transition target should be at least window token.
-            if (wc.asWindowState() != null) continue;
-            final ChangeInfo changeInfo = mChanges.get(wc);
-            // Reject no-ops
-            if (!changeInfo.hasChanged()) continue;
-            return false;
-        }
-        return true;
-    }
-
     @VisibleForTesting
     static class ChangeInfo {
         private static final int FLAG_NONE = 0;
diff --git a/services/core/java/com/android/server/wm/TrustedOverlayHost.java b/services/core/java/com/android/server/wm/TrustedOverlayHost.java
index f8edc2b..debe794 100644
--- a/services/core/java/com/android/server/wm/TrustedOverlayHost.java
+++ b/services/core/java/com/android/server/wm/TrustedOverlayHost.java
@@ -88,7 +88,17 @@
 
     void addOverlay(SurfaceControlViewHost.SurfacePackage p, SurfaceControl currentParent) {
         requireOverlaySurfaceControl();
-        mOverlays.add(p);
+
+        boolean hasExistingOverlay = false;
+        for (int i = mOverlays.size() - 1; i >= 0; i--) {
+            SurfaceControlViewHost.SurfacePackage l = mOverlays.get(i);
+            if (l.getSurfaceControl().isSameSurface(p.getSurfaceControl())) {
+                hasExistingOverlay = true;
+            }
+        }
+        if (!hasExistingOverlay) {
+            mOverlays.add(p);
+        }
 
         SurfaceControl.Transaction t = mWmService.mTransactionFactory.get();
         t.reparent(p.getSurfaceControl(), mSurfaceControl)
diff --git a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
index 1688a1a..817901f 100644
--- a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
+++ b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
@@ -32,7 +32,6 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.IntArray;
 import android.util.Pair;
 import android.util.Size;
@@ -159,10 +158,6 @@
 
     private InputWindowHandle[] mLastWindowHandles;
 
-    private final Object mIgnoredWindowTokensLock = new Object();
-
-    private final ArraySet<IBinder> mIgnoredWindowTokens = new ArraySet<>();
-
     private void startHandlerThreadIfNeeded() {
         synchronized (mHandlerThreadLock) {
             if (mHandler == null) {
@@ -173,18 +168,6 @@
         }
     }
 
-    void addIgnoredWindowTokens(IBinder token) {
-        synchronized (mIgnoredWindowTokensLock) {
-            mIgnoredWindowTokens.add(token);
-        }
-    }
-
-    void removeIgnoredWindowTokens(IBinder token) {
-        synchronized (mIgnoredWindowTokensLock) {
-            mIgnoredWindowTokens.remove(token);
-        }
-    }
-
     void registerListener(IBinder window, ITrustedPresentationListener listener,
             TrustedPresentationThresholds thresholds, int id) {
         startHandlerThreadIfNeeded();
@@ -271,12 +254,8 @@
 
         ArrayMap<ITrustedPresentationListener, Pair<IntArray, IntArray>> listenerUpdates =
                 new ArrayMap<>();
-        ArraySet<IBinder> ignoredWindowTokens;
-        synchronized (mIgnoredWindowTokensLock) {
-            ignoredWindowTokens = new ArraySet<>(mIgnoredWindowTokens);
-        }
         for (var windowHandle : mLastWindowHandles) {
-            if (ignoredWindowTokens.contains(windowHandle.getWindowToken())) {
+            if (!windowHandle.canOccludePresentation) {
                 ProtoLog.v(WM_DEBUG_TPL, "Skipping %s", windowHandle.name);
                 continue;
             }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index fc23700..554cbce 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -158,6 +158,7 @@
 import android.Manifest;
 import android.Manifest.permission;
 import android.animation.ValueAnimator;
+import android.annotation.EnforcePermission;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -3285,7 +3286,7 @@
         }
     }
 
-    @android.annotation.EnforcePermission(android.Manifest.permission.DISABLE_KEYGUARD)
+    @EnforcePermission(android.Manifest.permission.DISABLE_KEYGUARD)
     /**
      * @see android.app.KeyguardManager#exitKeyguardSecurely
      */
@@ -4513,7 +4514,7 @@
         }
     }
 
-    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
+    @EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
     @Override
     public SurfaceControl addShellRoot(int displayId, IWindow client,
             @WindowManager.ShellRootLayer int shellRootLayer) {
@@ -4532,7 +4533,7 @@
         }
     }
 
-    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
+    @EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
     @Override
     public void setShellRootAccessibilityWindow(int displayId,
             @WindowManager.ShellRootLayer int shellRootLayer, IWindow target) {
@@ -4555,7 +4556,7 @@
         }
     }
 
-    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
+    @EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
     @Override
     public void setDisplayWindowInsetsController(
             int displayId, IDisplayWindowInsetsController insetsController) {
@@ -4574,7 +4575,7 @@
         }
     }
 
-    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
+    @EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
     @Override
     public void updateDisplayWindowRequestedVisibleTypes(
             int displayId, @InsetsType int requestedVisibleTypes) {
@@ -5834,7 +5835,7 @@
         }
     }
 
-    @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    @EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     @Override
     public void setForcedDisplaySize(int displayId, int width, int height) {
         setForcedDisplaySize_enforcePermission();
@@ -5852,7 +5853,7 @@
         }
     }
 
-    @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    @EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     @Override
     public void setForcedDisplayScalingMode(int displayId, int mode) {
         setForcedDisplayScalingMode_enforcePermission();
@@ -5940,7 +5941,7 @@
         return changed;
     }
 
-    @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    @EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     @Override
     public void clearForcedDisplaySize(int displayId) {
         clearForcedDisplaySize_enforcePermission();
@@ -6003,7 +6004,7 @@
         return -1;
     }
 
-    @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    @EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     @Override
     public void setForcedDisplayDensityForUser(int displayId, int density, int userId) {
         setForcedDisplayDensityForUser_enforcePermission();
@@ -6029,7 +6030,7 @@
         }
     }
 
-    @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    @EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     @Override
     public void clearForcedDisplayDensityForUser(int displayId, int userId) {
         clearForcedDisplayDensityForUser_enforcePermission();
@@ -6529,7 +6530,7 @@
         }
     }
 
-    @android.annotation.EnforcePermission(android.Manifest.permission.STATUS_BAR)
+    @EnforcePermission(android.Manifest.permission.STATUS_BAR)
     public void setNavBarVirtualKeyHapticFeedbackEnabled(boolean enabled) {
         setNavBarVirtualKeyHapticFeedbackEnabled_enforcePermission();
 
@@ -6571,7 +6572,7 @@
         }
     }
 
-    @android.annotation.EnforcePermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
+    @EnforcePermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
     @Override
     public Region getCurrentImeTouchRegion() {
         getCurrentImeTouchRegion_enforcePermission();
@@ -8461,12 +8462,13 @@
                 SurfaceControlViewHost.SurfacePackage overlay) {
             if (overlay == null) {
                 throw new IllegalArgumentException("Invalid overlay passed in for task=" + taskId);
-            } else if (overlay.getSurfaceControl() == null
-                    || !overlay.getSurfaceControl().isValid()) {
-                throw new IllegalArgumentException(
-                        "Invalid overlay surfacecontrol passed in for task=" + taskId);
             }
             synchronized (mGlobalLock) {
+                if (overlay.getSurfaceControl() == null
+                    || !overlay.getSurfaceControl().isValid()) {
+                    throw new IllegalArgumentException(
+                            "Invalid overlay surfacecontrol passed in for task=" + taskId);
+                }
                 final Task task = mRoot.getRootTask(taskId);
                 if (task == null) {
                     throw new IllegalArgumentException("no task with taskId" + taskId);
@@ -9949,13 +9951,17 @@
         mTrustedPresentationListenerController.unregisterListener(listener, id);
     }
 
+    @EnforcePermission(android.Manifest.permission.DETECT_SCREEN_RECORDING)
     @Override
     public boolean registerScreenRecordingCallback(IScreenRecordingCallback callback) {
+        registerScreenRecordingCallback_enforcePermission();
         return mScreenRecordingCallbackController.register(callback);
     }
 
+    @EnforcePermission(android.Manifest.permission.DETECT_SCREEN_RECORDING)
     @Override
     public void unregisterScreenRecordingCallback(IScreenRecordingCallback callback) {
+        unregisterScreenRecordingCallback_enforcePermission();
         mScreenRecordingCallbackController.unregister(callback);
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 24e50c5..f5806c0 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1148,10 +1148,6 @@
             parentWindow.addChild(this, sWindowSubLayerComparator);
         }
 
-        if (token.mRoundedCornerOverlay) {
-            mWmService.mTrustedPresentationListenerController.addIgnoredWindowTokens(
-                    getWindowToken());
-        }
     }
 
     @Override
@@ -1163,6 +1159,9 @@
         if (secureWindowState()) {
             getPendingTransaction().setSecure(mSurfaceControl, isSecureLocked());
         }
+        // All apps should be considered as occluding when computing TrustedPresentation Thresholds.
+        final boolean canOccludePresentation = !mSession.mCanAddInternalSystemWindow;
+        getPendingTransaction().setCanOccludePresentation(mSurfaceControl, canOccludePresentation);
     }
 
     void updateTrustedOverlay() {
@@ -2344,9 +2343,6 @@
         mSession.onWindowRemoved(this);
         mWmService.postWindowRemoveCleanupLocked(this);
 
-        mWmService.mTrustedPresentationListenerController.removeIgnoredWindowTokens(
-                getWindowToken());
-
         consumeInsetsChange();
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index f96d9e8..9cdaec6 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -18,6 +18,7 @@
 
 import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
 
 import static com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR;
 
@@ -517,6 +518,16 @@
     }
 
     @Test
+    public void testAuthenticationStateListeners_onAuthenticationAcquired()
+            throws RemoteException {
+        final FingerprintAuthenticationClient client = createClient();
+        client.start(mCallback);
+        client.onAcquired(FINGERPRINT_ACQUIRED_START, 0);
+
+        verify(mAuthenticationStateListeners).onAuthenticationAcquired(any(), anyInt(), anyInt());
+    }
+
+    @Test
     public void testAuthenticationStateListeners_onAuthenticationSucceeded()
             throws RemoteException {
         mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
index 1c6d36b..ea84eb2 100644
--- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -36,6 +36,7 @@
 import android.os.IDumpstateListener;
 import android.os.Process;
 import android.os.RemoteException;
+import android.platform.test.annotations.RequiresFlagsDisabled;
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -48,6 +49,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -118,6 +120,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED)
     public void testBugreportFileManagerFileExists() {
         Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
         mBugreportFileManager.addBugreportFileForCaller(
@@ -137,6 +140,7 @@
 
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED)
+    @Ignore
     public void testBugreportFileManagerKeepFilesOnRetrieval() {
         Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
         mBugreportFileManager.addBugreportFileForCaller(
@@ -150,6 +154,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED)
     public void testBugreportFileManagerMultipleFiles() {
         Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
         mBugreportFileManager.addBugreportFileForCaller(
diff --git a/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java b/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java
index b8dcecd..e27bb4c 100644
--- a/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java
+++ b/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java
@@ -320,4 +320,10 @@
         mWindowInfosReportedListeners.add(listener);
         return this;
     }
+
+    @Override
+    public SurfaceControl.Transaction setCanOccludePresentation(SurfaceControl sc,
+                boolean canOccludePresentation) {
+        return this;
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index 839cf7c..c247c08 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -82,6 +82,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.IpcDataCache;
 import android.os.PowerManager;
 import android.os.PowerManagerInternal;
 import android.os.PowerSaveState;
@@ -174,6 +175,11 @@
 
 
     @Before
+    public void disableProcessCaches() {
+        IpcDataCache.disableForTestMode();
+    }
+
+    @Before
     public void setUp() {
         // The AIDL stub will use PermissionEnforcer to check permission from the caller.
         mPermissionEnforcer = new FakePermissionEnforcer();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 5d114f4..7462201 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -5202,7 +5202,7 @@
         // rules for a missing package, created a long time ago and deleted a long time ago
         config.deletedRules.put("del6", newZenRule("bad_pkg", twoMonthsAgo, twoMonthsAgo));
 
-        mZenModeHelper.onUserUnlocked(42); // copies config and cleans it up.
+        mZenModeHelper.onUserSwitched(42); // copies config and cleans it up.
 
         assertThat(mZenModeHelper.mConfig.automaticRules.keySet())
                 .containsExactly("ar1", "ar2", "ar3", "ar4");
diff --git a/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java b/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
index 56c3ec0..c2a5824 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
@@ -146,7 +146,7 @@
 
     private void setupMocks(boolean currentActivityAssistAllowed, boolean assistStructureAllowed,
             boolean assistScreenshotAllowed) throws Exception {
-        doReturn(currentActivityAssistAllowed).when(mAtm).isAssistDataAllowedOnCurrentActivity();
+        doReturn(currentActivityAssistAllowed).when(mAtm).isAssistDataAllowed();
         doReturn(assistStructureAllowed ? MODE_ALLOWED : MODE_ERRORED).when(mAppOpsManager)
                 .noteOpNoThrow(eq(OP_ASSIST_STRUCTURE), anyInt(), anyString(), any(), any());
         doReturn(assistScreenshotAllowed ? MODE_ALLOWED : MODE_ERRORED).when(mAppOpsManager)
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index a2105b0..9ee48d8 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -21,6 +21,7 @@
 import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.ElapsedRealtimeLong;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -56,6 +57,7 @@
 import com.android.internal.os.SomeArgs;
 import com.android.internal.telecom.IVideoCallback;
 import com.android.internal.telecom.IVideoProvider;
+import com.android.server.telecom.flags.Flags;
 
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
@@ -4019,9 +4021,12 @@
     }
 
     /**
+     * Retrieves the direction of this connection.
      * @return The direction of the call.
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
     public final @Call.Details.CallDirection int getCallDirection() {
         return mCallDirection;
     }