Merge "Do not show UMO resumption state in the hub" into main
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 aec2842..b17e3343 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -90,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";
@@ -53784,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();
@@ -53793,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>);
@@ -53812,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/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/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/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/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/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/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/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/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 21d2bf2..6be1be4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2645,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 -->
     <!-- ======================================== -->
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/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/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/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 32c7433..e99fcc9 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -911,6 +911,9 @@
     <!-- 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 854f6b9..a5b9e9e 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -94,7 +94,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 {
@@ -358,9 +358,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"
 }
 
@@ -377,3 +377,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/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/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/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/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 0f1340a..a39aabf 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
@@ -244,7 +245,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/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/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/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/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/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/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/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/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/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/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/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/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/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/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 2ea662c..cb00e44 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3726,6 +3726,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,
@@ -3774,26 +3798,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/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/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/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/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);