Merge "Fix NSSL falsing in all builds" 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..cdfa7c8 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/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)
[email protected]
 public class ActivityManager {
     private static String TAG = "ActivityManager";
 
@@ -966,6 +967,7 @@
      * Print capability bits in human-readable form.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static void printCapabilitiesSummary(PrintWriter pw, @ProcessCapability int caps) {
         pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0 ? 'L' : '-');
         pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0 ? 'C' : '-');
@@ -976,6 +978,7 @@
     }
 
     /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static void printCapabilitiesSummary(StringBuilder sb, @ProcessCapability int caps) {
         sb.append((caps & PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0 ? 'L' : '-');
         sb.append((caps & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0 ? 'C' : '-');
@@ -989,6 +992,7 @@
      * Print capability bits in human-readable form.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static void printCapabilitiesFull(PrintWriter pw, @ProcessCapability int caps) {
         printCapabilitiesSummary(pw, caps);
         final int remain = caps & ~PROCESS_CAPABILITY_ALL;
@@ -999,6 +1003,7 @@
     }
 
     /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static String getCapabilitiesSummary(@ProcessCapability int caps) {
         final StringBuilder sb = new StringBuilder();
         printCapabilitiesSummary(sb, caps);
@@ -1018,6 +1023,7 @@
      * @return the value of the corresponding enums.proto ProcessStateEnum value.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static final int processStateAmToProto(int amInt) {
         switch (amInt) {
             case PROCESS_STATE_UNKNOWN:
@@ -1078,16 +1084,19 @@
     public static final int MAX_PROCESS_STATE = PROCESS_STATE_NONEXISTENT;
 
     /** @hide Should this process state be considered a background state? */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static final boolean isProcStateBackground(int procState) {
         return procState >= PROCESS_STATE_TRANSIENT_BACKGROUND;
     }
 
     /** @hide Should this process state be considered in the cache? */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static final boolean isProcStateCached(int procState) {
         return procState >= PROCESS_STATE_CACHED_ACTIVITY;
     }
 
     /** @hide Is this a foreground service type? */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isForegroundService(int procState) {
         return procState == PROCESS_STATE_FOREGROUND_SERVICE;
     }
@@ -1161,10 +1170,25 @@
         mContext = context;
     }
 
+    private static volatile int sCurrentUser$ravenwood = UserHandle.USER_NULL;
+
+    /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
+    public static void init$ravenwood(int currentUser) {
+        sCurrentUser$ravenwood = currentUser;
+    }
+
+    /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
+    public static void reset$ravenwood() {
+        sCurrentUser$ravenwood = UserHandle.USER_NULL;
+    }
+
     /**
      * Returns whether the launch was successful.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static final boolean isStartResultSuccessful(int result) {
         return FIRST_START_SUCCESS_CODE <= result && result <= LAST_START_SUCCESS_CODE;
     }
@@ -1173,6 +1197,7 @@
      * Returns whether the launch result was a fatal error.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static final boolean isStartResultFatalError(int result) {
         return FIRST_START_FATAL_ERROR_CODE <= result && result <= LAST_START_FATAL_ERROR_CODE;
     }
@@ -1343,6 +1368,7 @@
     public @interface RestrictionLevel{}
 
     /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static String restrictionLevelToName(@RestrictionLevel int level) {
         switch (level) {
             case RESTRICTION_LEVEL_UNKNOWN:
@@ -4779,6 +4805,7 @@
      * Returns "true" if the user interface is currently being messed with
      * by a monkey.
      */
+    @android.ravenwood.annotation.RavenwoodReplace
     public static boolean isUserAMonkey() {
         try {
             return getService().isUserAMonkey();
@@ -4787,6 +4814,12 @@
         }
     }
 
+    /** @hide */
+    public static boolean isUserAMonkey$ravenwood() {
+        // Ravenwood environment is never considered a "monkey"
+        return false;
+    }
+
     /**
      * Returns "true" if device is running in a test harness.
      *
@@ -4973,6 +5006,7 @@
             "android.permission.INTERACT_ACROSS_USERS",
             "android.permission.INTERACT_ACROSS_USERS_FULL"
     })
+    @android.ravenwood.annotation.RavenwoodReplace
     public static int getCurrentUser() {
         try {
             return getService().getCurrentUserId();
@@ -4981,6 +5015,11 @@
         }
     }
 
+    /** @hide */
+    public static int getCurrentUser$ravenwood() {
+        return sCurrentUser$ravenwood;
+    }
+
     /**
      * @param userid the user's id. Zero indicates the default user.
      * @hide
@@ -5320,6 +5359,7 @@
     /**
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodReplace
     public static boolean isSystemReady() {
         if (!sSystemReady) {
             if (ActivityThread.isSystem()) {
@@ -5334,6 +5374,12 @@
         return sSystemReady;
     }
 
+    /** @hide */
+    public static boolean isSystemReady$ravenwood() {
+        // Ravenwood environment is always considered as booted and ready
+        return true;
+    }
+
     /**
      * @hide
      */
@@ -5661,11 +5707,13 @@
     }
 
     /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isProcStateConsideredInteraction(@ProcessState int procState) {
         return (procState <= PROCESS_STATE_TOP || procState == PROCESS_STATE_BOUND_TOP);
     }
 
     /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static String procStateToString(int procState) {
         final String procStateStr;
         switch (procState) {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index d705eeb..8883907 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5946,6 +5946,12 @@
                 // there is enough space to do so (and fall back to the left edge if not).
                 big.setInt(R.id.actions, "setCollapsibleIndentDimen",
                         R.dimen.call_notification_collapsible_indent);
+                if (CallStyle.USE_NEW_ACTION_LAYOUT) {
+                    if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
+                        Log.d(TAG, "setting evenly divided mode on action list");
+                    }
+                    big.setBoolean(R.id.actions, "setEvenlyDividedMode", true);
+                }
             }
             big.setBoolean(R.id.actions, "setEmphasizedMode", emphasizedMode);
             if (numActions > 0 && !p.mHideActions) {
@@ -6421,7 +6427,15 @@
                     // Remove full-length color spans and ensure text contrast with the button fill.
                     title = ContrastColorUtil.ensureColorSpanContrast(title, buttonFillColor);
                 }
-                button.setTextViewText(R.id.action0, ensureColorSpanContrast(title, p));
+                final CharSequence label = ensureColorSpanContrast(title, p);
+                if (p.mCallStyleActions && CallStyle.USE_NEW_ACTION_LAYOUT) {
+                    if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
+                        Log.d(TAG, "new action layout enabled, gluing instead of setting text");
+                    }
+                    button.setCharSequence(R.id.action0, "glueLabel", label);
+                } else {
+                    button.setTextViewText(R.id.action0, label);
+                }
                 int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
                         buttonFillColor, mInNightMode);
                 if (tombstone) {
@@ -6438,7 +6452,14 @@
                 button.setColorStateList(R.id.action0, "setButtonBackground",
                         ColorStateList.valueOf(buttonFillColor));
                 if (p.mCallStyleActions) {
-                    button.setImageViewIcon(R.id.action0, action.getIcon());
+                    if (CallStyle.USE_NEW_ACTION_LAYOUT) {
+                        if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
+                            Log.d(TAG, "new action layout enabled, gluing instead of setting icon");
+                        }
+                        button.setIcon(R.id.action0, "glueIcon", action.getIcon());
+                    } else {
+                        button.setImageViewIcon(R.id.action0, action.getIcon());
+                    }
                     boolean priority = action.getExtras().getBoolean(CallStyle.KEY_ACTION_PRIORITY);
                     button.setBoolean(R.id.action0, "setIsPriority", priority);
                     int minWidthDimen =
@@ -9565,6 +9586,15 @@
      * </pre>
      */
     public static class CallStyle extends Style {
+        /**
+         * @hide
+         */
+        public static final boolean USE_NEW_ACTION_LAYOUT = false;
+
+        /**
+         * @hide
+         */
+        public static final boolean DEBUG_NEW_ACTION_LAYOUT = true;
 
         /**
          * @hide
diff --git a/core/java/android/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.
  */
[email protected]
 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/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/com/android/internal/widget/EmphasizedNotificationButton.java b/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
index ce6af49..5cda3f2 100644
--- a/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
+++ b/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
@@ -16,16 +16,30 @@
 
 package com.android.internal.widget;
 
+import static android.app.Notification.CallStyle.DEBUG_NEW_ACTION_LAYOUT;
+import static android.app.Notification.CallStyle.USE_NEW_ACTION_LAYOUT;
+import static android.text.style.DynamicDrawableSpan.ALIGN_CENTER;
+
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
 import android.graphics.BlendMode;
+import android.graphics.Canvas;
+import android.graphics.Paint;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.DrawableWrapper;
 import android.graphics.drawable.GradientDrawable;
 import android.graphics.drawable.Icon;
 import android.graphics.drawable.RippleDrawable;
+import android.text.SpannableStringBuilder;
+import android.text.TextPaint;
+import android.text.style.ImageSpan;
+import android.text.style.MetricAffectingSpan;
+import android.text.style.ReplacementSpan;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.RemotableViewMethod;
 import android.widget.Button;
 import android.widget.RemoteViews;
@@ -43,6 +57,14 @@
     private final GradientDrawable mBackground;
     private boolean mPriority;
 
+    private int mInitialDrawablePadding;
+    private int mIconSize;
+
+    private Drawable mIconToGlue;
+    private CharSequence mLabelToGlue;
+    private int mGluedLayoutDirection = LAYOUT_DIRECTION_UNDEFINED;
+    private boolean mGluePending;
+
     public EmphasizedNotificationButton(Context context) {
         this(context, null);
     }
@@ -58,10 +80,25 @@
     public EmphasizedNotificationButton(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
+
         mRipple = (RippleDrawable) getBackground();
         mRipple.mutate();
         DrawableWrapper inset = (DrawableWrapper) mRipple.getDrawable(0);
         mBackground = (GradientDrawable) inset.getDrawable();
+
+        mIconSize = mContext.getResources().getDimensionPixelSize(
+                R.dimen.notification_actions_icon_drawable_size);
+
+        try (TypedArray typedArray = context.obtainStyledAttributes(
+                attrs, android.R.styleable.TextView, defStyleAttr, defStyleRes)) {
+            mInitialDrawablePadding = typedArray.getDimensionPixelSize(
+                    android.R.styleable.TextView_drawablePadding, 0);
+        }
+
+        if (DEBUG_NEW_ACTION_LAYOUT) {
+            Log.v(TAG, "iconSize = " + mIconSize + "px, "
+                    + "initialDrawablePadding = " + mInitialDrawablePadding + "px");
+        }
     }
 
     @RemotableViewMethod
@@ -95,19 +132,248 @@
         return () -> setImageDrawable(drawable);
     }
 
-    private void setImageDrawable(Drawable drawable) {
+    private void setImageDrawable(@Nullable Drawable drawable) {
         if (drawable != null) {
-            drawable.mutate();
-            drawable.setTintList(getTextColors());
-            drawable.setTintBlendMode(BlendMode.SRC_IN);
-            int iconSize = mContext.getResources().getDimensionPixelSize(
-                    R.dimen.notification_actions_icon_drawable_size);
-            drawable.setBounds(0, 0, iconSize, iconSize);
+            prepareIcon(drawable);
         }
         setCompoundDrawablesRelative(drawable, null, null, null);
     }
 
     /**
+     * Sets an icon to be 'glued' to the label when this button is displayed, so the icon will stay
+     * with the text if the button is wider than needed and the text isn't start-aligned.
+     *
+     * As with {@link #setImageIcon(Icon)}, the Icon will have its size constrained and will be set
+     * to the same color as the text, and this must be called after {@link #setTextColor(int)} for
+     * the latter to work.
+     *
+     * This must be called along with {@link #glueLabel(CharSequence)}, in any order, before the
+     * button is displayed.
+     */
+    @RemotableViewMethod(asyncImpl = "glueIconAsync")
+    public void glueIcon(@Nullable Icon icon) {
+        final Drawable drawable = icon == null ? null : icon.loadDrawable(mContext);
+        setIconToGlue(drawable);
+    }
+
+    /**
+     * @hide
+     */
+    @RemotableViewMethod
+    public Runnable glueIconAsync(@Nullable Icon icon) {
+        final Drawable drawable = icon == null ? null : icon.loadDrawable(mContext);
+        return () -> setIconToGlue(drawable);
+    }
+
+    private void setIconToGlue(@Nullable Drawable icon) {
+        if (!USE_NEW_ACTION_LAYOUT) {
+            Log.e(TAG, "glueIcon: new action layout disabled; doing nothing");
+            return;
+        }
+
+        prepareIcon(icon);
+
+        mIconToGlue = icon;
+        mGluePending = true;
+
+        glueIconAndLabelIfNeeded();
+    }
+
+    private void prepareIcon(@NonNull Drawable drawable) {
+        drawable.mutate();
+        drawable.setTintList(getTextColors());
+        drawable.setTintBlendMode(BlendMode.SRC_IN);
+        drawable.setBounds(0, 0, mIconSize, mIconSize);
+    }
+
+    /**
+     * Sets a label to be 'glued' to the icon when this button is displayed, so the icon will stay
+     * with the text if the button is wider than needed and the text isn't start-aligned.
+     *
+     * This must be called along with {@link #glueIcon(Icon)}, in any order, before the button is
+     * displayed.
+     */
+    @RemotableViewMethod(asyncImpl = "glueLabelAsync")
+    public void glueLabel(@Nullable CharSequence label) {
+        setLabelToGlue(label);
+    }
+
+    /**
+     * @hide
+     */
+    @RemotableViewMethod
+    public Runnable glueLabelAsync(@Nullable CharSequence label) {
+        return () -> setLabelToGlue(label);
+    }
+
+    private void setLabelToGlue(@Nullable CharSequence label) {
+        if (!USE_NEW_ACTION_LAYOUT) {
+            Log.e(TAG, "glueLabel: new action layout disabled; doing nothing");
+            return;
+        }
+
+        mLabelToGlue = label;
+        mGluePending = true;
+
+        glueIconAndLabelIfNeeded();
+    }
+
+    @Override
+    public void onRtlPropertiesChanged(int layoutDirection) {
+        super.onRtlPropertiesChanged(layoutDirection);
+
+        if (DEBUG_NEW_ACTION_LAYOUT) {
+            Log.v(TAG, "onRtlPropertiesChanged: layoutDirection = " + layoutDirection + ", "
+                    + "gluedLayoutDirection = " + mGluedLayoutDirection);
+        }
+
+        if (layoutDirection != mGluedLayoutDirection) {
+            if (DEBUG_NEW_ACTION_LAYOUT) {
+                Log.d(TAG, "onRtlPropertiesChanged: layout direction changed; regluing");
+            }
+            mGluePending = true;
+        }
+
+        glueIconAndLabelIfNeeded();
+    }
+
+    private void glueIconAndLabelIfNeeded() {
+        // Don't need to glue:
+
+        if (!mGluePending) {
+            if (DEBUG_NEW_ACTION_LAYOUT) {
+                Log.v(TAG, "glueIconAndLabelIfNeeded: glue not pending; doing nothing");
+            }
+            return;
+        }
+
+        if (mIconToGlue == null && mLabelToGlue == null) {
+            if (DEBUG_NEW_ACTION_LAYOUT) {
+                Log.v(TAG, "glueIconAndLabelIfNeeded: no icon or label to glue; doing nothing");
+            }
+            mGluePending = false;
+            return;
+        }
+
+        if (!USE_NEW_ACTION_LAYOUT) {
+            Log.e(TAG, "glueIconAndLabelIfNeeded: new action layout disabled; doing nothing");
+            return;
+        }
+
+        // Not ready to glue yet:
+
+        if (!isLayoutDirectionResolved()) {
+            if (DEBUG_NEW_ACTION_LAYOUT) {
+                Log.v(TAG, "glueIconAndLabelIfNeeded: "
+                        + "layout direction not resolved; doing nothing");
+            }
+            return;
+        }
+
+        // Ready to glue but don't have an icon *and* a label:
+        //
+        // (Note that this will *not* happen while the button is being initialized, since we won't
+        // be ready to glue. This can only happen if the button is initialized and displayed and
+        // *then* someone calls glueIcon or glueLabel.
+
+        if (mIconToGlue == null) {
+            Log.w(TAG, "glueIconAndLabelIfNeeded: label glued without icon; doing nothing");
+            return;
+        }
+
+        if (mLabelToGlue == null) {
+            Log.w(TAG, "glueIconAndLabelIfNeeded: icon glued without label; doing nothing");
+            return;
+        }
+
+        // Can't glue:
+
+        final int layoutDirection = getLayoutDirection();
+        if (layoutDirection != LAYOUT_DIRECTION_LTR && layoutDirection != LAYOUT_DIRECTION_RTL) {
+            Log.e(TAG, "glueIconAndLabelIfNeeded: "
+                    + "resolved layout direction neither LTR nor RTL; "
+                    + "doing nothing");
+            return;
+        }
+
+        // No excuses left, let's glue it!
+
+        glueIconAndLabel(layoutDirection);
+
+        mGluePending = false;
+        mGluedLayoutDirection = layoutDirection;
+    }
+
+    // Unicode replacement character
+    private static final String IMAGE_SPAN_TEXT = "\ufffd";
+
+    // Unicode no-break space
+    private static final String SPACER_SPAN_TEXT = "\u00a0";
+
+    private static final String LEFT_TO_RIGHT_ISOLATE = "\u2066";
+    private static final String RIGHT_TO_LEFT_ISOLATE = "\u2067";
+    private static final String FIRST_STRONG_ISOLATE = "\u2068";
+    private static final String POP_DIRECTIONAL_ISOLATE = "\u2069";
+
+    private void glueIconAndLabel(int layoutDirection) {
+        final boolean rtlLayout = layoutDirection == LAYOUT_DIRECTION_RTL;
+
+        if (DEBUG_NEW_ACTION_LAYOUT) {
+            Log.d(TAG, "glueIconAndLabel: "
+                    + "icon = " + mIconToGlue + ", "
+                    + "iconSize = " + mIconSize + "px, "
+                    + "initialDrawablePadding = " + mInitialDrawablePadding + "px, "
+                    + "labelToGlue.length = " + mLabelToGlue.length() + ", "
+                    + "rtlLayout = " + rtlLayout);
+        }
+
+        logIfTextDirectionNotFirstStrong();
+
+        final SpannableStringBuilder builder = new SpannableStringBuilder();
+
+        // The text direction of the label might not match the layout direction of the button, so
+        // wrap the entire string in a LEFT-TO-RIGHT ISOLATE or RIGHT-TO-LEFT ISOLATE to match the
+        // layout direction. This puts the icon, padding, and label in the right order.
+        builder.append(rtlLayout ? RIGHT_TO_LEFT_ISOLATE : LEFT_TO_RIGHT_ISOLATE);
+
+        appendSpan(builder, IMAGE_SPAN_TEXT, new ImageSpan(mIconToGlue, ALIGN_CENTER));
+        appendSpan(builder, SPACER_SPAN_TEXT, new SpacerSpan(mInitialDrawablePadding));
+
+        // If the text and layout directions are different, we would end up with the *label* in the
+        // wrong direction, so wrap the label in a FIRST STRONG ISOLATE. This triggers the same
+        // automatic text direction heuristic that Android uses by default.
+        builder.append(FIRST_STRONG_ISOLATE);
+
+        appendSpan(builder, mLabelToGlue, new CenterBesideImageSpan(mIconSize));
+
+        builder.append(POP_DIRECTIONAL_ISOLATE);
+        builder.append(POP_DIRECTIONAL_ISOLATE);
+
+        setText(builder);
+    }
+
+    private void logIfTextDirectionNotFirstStrong() {
+        if (!isTextDirectionResolved()) {
+            Log.e(TAG, "glueIconAndLabel: text direction not resolved; "
+                    + "letting View assume FIRST STRONG");
+        }
+        final int textDirection = getTextDirection();
+        if (textDirection != TEXT_DIRECTION_FIRST_STRONG) {
+            Log.w(TAG, "glueIconAndLabel: "
+                    + "expected text direction TEXT_DIRECTION_FIRST_STRONG "
+                    + "but found " + textDirection + "; "
+                    + "will use a FIRST STRONG ISOLATE regardless");
+        }
+    }
+
+    private void appendSpan(SpannableStringBuilder builder, CharSequence text, Object span) {
+        final int spanStart = builder.length();
+        builder.append(text);
+        final int spanEnd = builder.length();
+        builder.setSpan(span, spanStart, spanEnd, 0);
+    }
+
+    /**
      * Sets whether this view is a priority over its peers (which affects width).
      * Specifically, this is used by {@link NotificationActionListLayout} to give this view width
      * priority ahead of user-defined buttons when allocating horizontal space.
@@ -123,4 +389,104 @@
     public boolean isPriority() {
         return mPriority;
     }
+
+    private static class SpacerSpan extends ReplacementSpan {
+        private int mWidth;
+
+        SpacerSpan(int width) {
+            mWidth = width;
+
+            if (DEBUG_NEW_ACTION_LAYOUT) {
+                Log.d(TAG, "width = " + mWidth + "px");
+            }
+        }
+
+
+        @Override
+        public int getSize(@NonNull Paint paint, CharSequence text, int start, int end,
+                           @Nullable Paint.FontMetricsInt fontMetrics) {
+            if (DEBUG_NEW_ACTION_LAYOUT) {
+                Log.v(TAG, "getSize returning " + mWidth + "px");
+            }
+
+            return mWidth;
+        }
+
+        @Override
+        public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end,
+                         float x, int top, int y, int bottom, @NonNull Paint paint) {
+            if (DEBUG_NEW_ACTION_LAYOUT) {
+                Log.v(TAG, "drawing nothing");
+            }
+
+            // Draw nothing, it's a spacer.
+        }
+
+        private static final String TAG = "SpacerSpan";
+    }
+
+    private static class CenterBesideImageSpan extends MetricAffectingSpan {
+        private int mImageHeight;
+
+        private boolean mMeasured;
+        private int mBaselineShiftOffset;
+
+        CenterBesideImageSpan(int imageHeight) {
+            mImageHeight = imageHeight;
+
+            if (DEBUG_NEW_ACTION_LAYOUT) {
+                Log.d(TAG, "imageHeight = " + mImageHeight + "px");
+            }
+        }
+
+        @Override
+        public void updateMeasureState(@NonNull TextPaint textPaint) {
+            final int textHeight = (int) -textPaint.ascent();
+
+            /*
+             * We only need to shift the text *up* if the text is shorter than the image; ImageSpan
+             * with ALIGN_CENTER will shift the *image* up if the text is taller than the image.
+             */
+            if (textHeight < mImageHeight) {
+                mBaselineShiftOffset = -(mImageHeight - textHeight) / 2;
+            } else {
+                mBaselineShiftOffset = 0;
+            }
+
+            mMeasured = true;
+
+            if (DEBUG_NEW_ACTION_LAYOUT) {
+                Log.d(TAG, "updateMeasureState: "
+                        + "imageHeight = " + mImageHeight + "px, "
+                        + "textHeight = " + textHeight + "px, "
+                        + "baselineShiftOffset = " + mBaselineShiftOffset + "px");
+            }
+
+            textPaint.baselineShift += mBaselineShiftOffset;
+        }
+
+        @Override
+        public void updateDrawState(TextPaint textPaint) {
+            if (textPaint == null) {
+                Log.e(TAG, "updateDrawState: textPaint is null; doing nothing");
+                return;
+            }
+
+            if (!mMeasured) {
+                Log.e(TAG, "updateDrawState: called without measure; doing nothing");
+                return;
+            }
+
+            if (DEBUG_NEW_ACTION_LAYOUT) {
+                Log.v(TAG, "updateDrawState: "
+                        + "baselineShiftOffset = " + mBaselineShiftOffset + "px");
+            }
+
+            textPaint.baselineShift += mBaselineShiftOffset;
+        }
+
+        private static final String TAG = "CenterBesideImageSpan";
+    }
+
+    private static final String TAG = "EmphasizedNotificationButton";
 }
diff --git a/core/java/com/android/internal/widget/NotificationActionListLayout.java b/core/java/com/android/internal/widget/NotificationActionListLayout.java
index a7a69c9..69d2544 100644
--- a/core/java/com/android/internal/widget/NotificationActionListLayout.java
+++ b/core/java/com/android/internal/widget/NotificationActionListLayout.java
@@ -16,12 +16,16 @@
 
 package com.android.internal.widget;
 
+import static android.app.Notification.CallStyle.DEBUG_NEW_ACTION_LAYOUT;
+import static android.app.Notification.CallStyle.USE_NEW_ACTION_LAYOUT;
+
 import android.annotation.DimenRes;
 import android.app.Notification;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.RippleDrawable;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.Gravity;
 import android.view.RemotableViewMethod;
 import android.view.View;
@@ -41,13 +45,13 @@
  */
 @RemoteViews.RemoteView
 public class NotificationActionListLayout extends LinearLayout {
-
     private final int mGravity;
     private int mTotalWidth = 0;
     private int mExtraStartPadding = 0;
     private ArrayList<TextViewInfo> mMeasureOrderTextViews = new ArrayList<>();
     private ArrayList<View> mMeasureOrderOther = new ArrayList<>();
     private boolean mEmphasizedMode;
+    private boolean mEvenlyDividedMode;
     private int mDefaultPaddingBottom;
     private int mDefaultPaddingTop;
     private int mEmphasizedPaddingTop;
@@ -124,6 +128,42 @@
         }
     }
 
+    private int measureAndReturnEvenlyDividedWidth(int heightMeasureSpec, int innerWidth) {
+        final int numChildren = getChildCount();
+        int childMarginSum = 0;
+        for (int i = 0; i < numChildren; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+                childMarginSum += lp.leftMargin + lp.rightMargin;
+            }
+        }
+
+        final int innerWidthMinusChildMargins = innerWidth - childMarginSum;
+        final int childWidth = innerWidthMinusChildMargins / mNumNotGoneChildren;
+        final int childWidthMeasureSpec =
+                MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
+
+        if (DEBUG_NEW_ACTION_LAYOUT) {
+            Log.v(TAG, "measuring evenly divided width: "
+                    + "numChildren = " + numChildren + ", "
+                    + "innerWidth = " + innerWidth + "px, "
+                    + "childMarginSum = " + childMarginSum + "px, "
+                    + "innerWidthMinusChildMargins = " + innerWidthMinusChildMargins + "px, "
+                    + "childWidth = " + childWidth + "px, "
+                    + "childWidthMeasureSpec = " + MeasureSpec.toString(childWidthMeasureSpec));
+        }
+
+        for (int i = 0; i < numChildren; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                child.measure(childWidthMeasureSpec, heightMeasureSpec);
+            }
+        }
+
+        return innerWidth;
+    }
+
     private int measureAndGetUsedWidth(int widthMeasureSpec, int heightMeasureSpec, int innerWidth,
             boolean collapsePriorityActions) {
         final int numChildren = getChildCount();
@@ -208,11 +248,16 @@
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         countAndRebuildMeasureOrder();
         final int innerWidth = MeasureSpec.getSize(widthMeasureSpec) - mPaddingLeft - mPaddingRight;
-        int usedWidth = measureAndGetUsedWidth(widthMeasureSpec, heightMeasureSpec, innerWidth,
-                false /* collapsePriorityButtons */);
-        if (mNumPriorityChildren != 0 && usedWidth >= innerWidth) {
+        int usedWidth;
+        if (mEvenlyDividedMode) {
+            usedWidth = measureAndReturnEvenlyDividedWidth(heightMeasureSpec, innerWidth);
+        } else {
             usedWidth = measureAndGetUsedWidth(widthMeasureSpec, heightMeasureSpec, innerWidth,
-                    true /* collapsePriorityButtons */);
+                    false /* collapsePriorityButtons */);
+            if (mNumPriorityChildren != 0 && usedWidth >= innerWidth) {
+                usedWidth = measureAndGetUsedWidth(widthMeasureSpec, heightMeasureSpec, innerWidth,
+                        true /* collapsePriorityButtons */);
+            }
         }
 
         mTotalWidth = usedWidth + mPaddingRight + mPaddingLeft + mExtraStartPadding;
@@ -352,6 +397,38 @@
     }
 
     /**
+     * Sets whether the available width should be distributed evenly among the action buttons.
+     *
+     * When enabled, the available width (after subtracting this layout's padding and all of the
+     * buttons' margins) is divided by the number of (not-GONE) buttons, and each button is forced
+     * to that exact width, even if it is less <em>or more</em> width than they need.
+     *
+     * When disabled, the available width is allocated as buttons need; if that exceeds the
+     * available width, priority buttons are collapsed to just their icon to save space.
+     *
+     * @param evenlyDividedMode whether to enable evenly divided mode
+     */
+    @RemotableViewMethod
+    public void setEvenlyDividedMode(boolean evenlyDividedMode) {
+        if (evenlyDividedMode && !USE_NEW_ACTION_LAYOUT) {
+            Log.e(TAG, "setEvenlyDividedMode(true) called with new action layout disabled; "
+                    + "leaving evenly divided mode disabled");
+            return;
+        }
+
+        if (evenlyDividedMode == mEvenlyDividedMode) {
+            return;
+        }
+
+        if (DEBUG_NEW_ACTION_LAYOUT) {
+            Log.v(TAG, "evenlyDividedMode changed to " + evenlyDividedMode + "; "
+                    + "requesting layout");
+        }
+        mEvenlyDividedMode = evenlyDividedMode;
+        requestLayout();
+    }
+
+    /**
      * Set whether the list is in a mode where some actions are emphasized. This will trigger an
      * equal measuring where all actions are full height and change a few parameters like
      * the padding.
@@ -410,4 +487,5 @@
         }
     }
 
+    private static final String TAG = "NotificationActionListLayout";
 }
diff --git a/core/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 @@
[email protected]
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..f5a81c58
--- /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/media/java/android/media/BluetoothProfileConnectionInfo.java b/media/java/android/media/BluetoothProfileConnectionInfo.java
index e4dc152..0613fc65 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/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index a7d6bfb..ba3026e 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -104,7 +104,7 @@
         " standard background color is desired.  This was the behavior before we discovered"
         " a resources threading issue, which we worked around by tinting the notification"
         " backgrounds and footer buttons."
-    bug: "294347738"
+    bug: "294830092"
 }
 
 flag {
@@ -368,9 +368,9 @@
 }
 
 flag {
-   name: "enable_keyguard_compose"
+   name: "compose_lockscreen"
    namespace: "systemui"
-   description: "Enables the compose version of keyguard."
+   description: "Enables the compose version of lockscreen that runs standalone, outside of Flexiglass."
    bug: "301968149"
 }
 
@@ -387,3 +387,11 @@
    description: "Enables on-screen contextual tip about how to take screenshot."
    bug: "322891421"
 }
+
+flag {
+   name: "shaderlib_loading_effect_refactor"
+   namespace: "systemui"
+   description: "Extend shader library to provide the common loading effects."
+   bug: "282007590"
+}
+
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt
index 68e57b5..071433e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt
@@ -53,7 +53,7 @@
             stickyKeys.forEach { (key, isLocked) ->
                 key(key) {
                     Text(
-                        text = key.text,
+                        text = key.displayedText,
                         fontWeight = if (isLocked.locked) FontWeight.Bold else FontWeight.Normal
                     )
                 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
index 1642e52..45f98be 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
@@ -72,7 +72,7 @@
         testScope.runTest {
             val mediaModel = collectLastValue(underTest.mediaModel)
             runCurrent()
-            assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
+            assertThat(mediaModel()?.hasActiveMediaOrRecommendation).isFalse()
         }
 
     @Test
@@ -84,16 +84,16 @@
             // Initial value is false.
             val mediaModel = collectLastValue(underTest.mediaModel)
             runCurrent()
-            assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
+            assertThat(mediaModel()?.hasActiveMediaOrRecommendation).isFalse()
 
             // Change to media available and notify the listener.
-            whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
+            whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true)
             whenever(mediaData.createdTimestampMillis).thenReturn(1234L)
             mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)
             runCurrent()
 
             // Media active now returns true.
-            assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isTrue()
+            assertThat(mediaModel()?.hasActiveMediaOrRecommendation).isTrue()
             assertThat(mediaModel()?.createdTimestampMillis).isEqualTo(1234L)
         }
 
@@ -104,20 +104,20 @@
             verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
 
             // Change to media available and notify the listener.
-            whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
+            whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true)
             mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)
             runCurrent()
 
             // Media active now returns true.
             val mediaModel = collectLastValue(underTest.mediaModel)
-            assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isTrue()
+            assertThat(mediaModel()?.hasActiveMediaOrRecommendation).isTrue()
 
             // Change to media unavailable and notify the listener.
-            whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(false)
+            whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(false)
             mediaDataListenerCaptor.value.onMediaDataRemoved("key")
             runCurrent()
 
             // Media active now returns false.
-            assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
+            assertThat(mediaModel()?.hasActiveMediaOrRecommendation).isFalse()
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/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/SideFpsOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
index ce72603..cfda75c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
@@ -41,12 +41,14 @@
 import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
 import com.android.systemui.res.R
 import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 
 /** Models UI of the side fingerprint sensor indicator view. */
+@OptIn(ExperimentalCoroutinesApi::class)
 class SideFpsOverlayViewModel
 @Inject
 constructor(
@@ -176,8 +178,8 @@
     val lottieCallbacks: Flow<List<LottieCallback>> =
         combine(
             biometricStatusInteractor.sfpsAuthenticationReason,
-            deviceEntrySideFpsOverlayInteractor.showIndicatorForDeviceEntry.distinctUntilChanged(),
-            sideFpsProgressBarViewModel.isVisible,
+            deviceEntrySideFpsOverlayInteractor.showIndicatorForDeviceEntry,
+            sideFpsProgressBarViewModel.isVisible
         ) { reason: AuthenticationReason, showIndicatorForDeviceEntry: Boolean, progressBarIsVisible
             ->
             val callbacks = mutableListOf<LottieCallback>()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
index c46f0d1..33edb80 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
@@ -21,21 +21,21 @@
 
 /** Data model of media on the communal hub. */
 data class CommunalMediaModel(
-    val hasAnyMediaOrRecommendation: Boolean,
+    val hasActiveMediaOrRecommendation: Boolean,
     val createdTimestampMillis: Long = 0L,
 ) : Diffable<CommunalMediaModel> {
     companion object {
         val INACTIVE =
             CommunalMediaModel(
-                hasAnyMediaOrRecommendation = false,
+                hasActiveMediaOrRecommendation = false,
             )
     }
 
     override fun logDiffs(prevVal: CommunalMediaModel, row: TableRowLogger) {
-        if (hasAnyMediaOrRecommendation != prevVal.hasAnyMediaOrRecommendation) {
+        if (hasActiveMediaOrRecommendation != prevVal.hasActiveMediaOrRecommendation) {
             row.logChange(
                 columnName = "isMediaActive",
-                value = hasAnyMediaOrRecommendation,
+                value = hasActiveMediaOrRecommendation,
             )
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
index 2b66491..201be51 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
@@ -73,10 +73,10 @@
         )
 
     private fun updateMediaModel(data: MediaData? = null) {
-        if (mediaDataManager.hasAnyMediaOrRecommendation()) {
+        if (mediaDataManager.hasActiveMediaOrRecommendation()) {
             _mediaModel.value =
                 CommunalMediaModel(
-                    hasAnyMediaOrRecommendation = true,
+                    hasActiveMediaOrRecommendation = true,
                     createdTimestampMillis = data?.createdTimestampMillis ?: 0L,
                 )
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 75a27a2..950ac3c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -318,7 +318,7 @@
             )
 
             // Add UMO
-            if (media.hasAnyMediaOrRecommendation) {
+            if (media.hasActiveMediaOrRecommendation) {
                 ongoingContent.add(
                     CommunalContentModel.Umo(
                         createdTimestampMillis = media.createdTimestampMillis,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 0c12841..40d2d16 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -87,7 +87,7 @@
         with(mediaHost) {
             expansion = MediaHostState.EXPANDED
             expandedMatchesParentHeight = true
-            showsOnlyActiveMedia = false
+            showsOnlyActiveMedia = true
             falsingProtectionNeeded = false
             init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/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/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/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/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/utils/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryKosmos.kt
index 961022f..a4f28f3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryKosmos.kt
@@ -20,4 +20,4 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 
-val Kosmos.biometricStatusRepository by Fixture { FakeBiometricStatusRepository() }
+var Kosmos.biometricStatusRepository by Fixture { FakeBiometricStatusRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeBiometricStatusRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeBiometricStatusRepository.kt
index 1c8bd3b..e9b7a69 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeBiometricStatusRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeBiometricStatusRepository.kt
@@ -18,9 +18,12 @@
 package com.android.systemui.biometrics.data.repository
 
 import com.android.systemui.biometrics.shared.model.AuthenticationReason
+import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
 
 class FakeBiometricStatusRepository : BiometricStatusRepository {
     private val _fingerprintAuthenticationReason =
@@ -28,7 +31,16 @@
     override val fingerprintAuthenticationReason: StateFlow<AuthenticationReason> =
         _fingerprintAuthenticationReason.asStateFlow()
 
+    private val _fingerprintAcquiredStatus =
+        MutableStateFlow<FingerprintAuthenticationStatus?>(null)
+    override val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus> =
+        _fingerprintAcquiredStatus.asStateFlow().filterNotNull()
+
     fun setFingerprintAuthenticationReason(reason: AuthenticationReason) {
         _fingerprintAuthenticationReason.value = reason
     }
+
+    fun setFingerprintAcquiredStatus(status: FingerprintAuthenticationStatus) {
+        _fingerprintAcquiredStatus.value = status
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
index 3ea3ccf..1884a32 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
@@ -28,7 +28,7 @@
     fun mediaActive(timestamp: Long = 0L) {
         _mediaModel.value =
             CommunalMediaModel(
-                hasAnyMediaOrRecommendation = true,
+                hasActiveMediaOrRecommendation = true,
                 createdTimestampMillis = timestamp,
             )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/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/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/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/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);