Merge "Delete the built-in "Sleeping" rule when a TYPE_BEDTIME rule is added" into main
diff --git a/Android.bp b/Android.bp
index 82b844b..f3b2ebb 100644
--- a/Android.bp
+++ b/Android.bp
@@ -665,6 +665,7 @@
     lint: {
         baseline_filename: "lint-baseline.xml",
     },
+    apex_available: ["com.android.wifi"],
 }
 
 filegroup {
diff --git a/WEAR_OWNERS b/WEAR_OWNERS
index 4f3bc27..4127f99 100644
--- a/WEAR_OWNERS
+++ b/WEAR_OWNERS
@@ -4,3 +4,9 @@
 adsule@google.com
 andriyn@google.com
 yfz@google.com
+con@google.com
+leetodd@google.com
+sadrul@google.com
+rwmyers@google.com
+nalmalki@google.com
+shijianli@google.com
diff --git a/apct-tests/perftests/core/src/android/database/SQLiteDatabasePerfTest.java b/apct-tests/perftests/core/src/android/database/SQLiteDatabasePerfTest.java
index b7460cd..fa4387b 100644
--- a/apct-tests/perftests/core/src/android/database/SQLiteDatabasePerfTest.java
+++ b/apct-tests/perftests/core/src/android/database/SQLiteDatabasePerfTest.java
@@ -433,6 +433,17 @@
         performMultithreadedReadWriteTest();
     }
 
+    /**
+     * This test measures a multi-threaded read-write environment where there are 2 readers and
+     * 1 writer in the database using WAL journal mode and NORMAL syncMode.
+     */
+    @Test
+    public void testMultithreadedReadWriteWithWalNormal() {
+        recreateTestDatabase(SQLiteDatabase.JOURNAL_MODE_WAL, SQLiteDatabase.SYNC_MODE_NORMAL);
+        insertT1TestDataSet();
+        performMultithreadedReadWriteTest();
+    }
+
     private void doReadLoop(int totalIterations) {
         Random rnd = new Random(0);
         int currentIteration = 0;
@@ -472,7 +483,6 @@
     }
 
     private void doUpdateLoop(int totalIterations) {
-        SQLiteDatabase db = mContext.openOrCreateDatabase(DB_NAME, Context.MODE_PRIVATE, null);
         Random rnd = new Random(0);
         int i = 0;
         ContentValues cv = new ContentValues();
@@ -485,24 +495,12 @@
             cv.put("COL_B", "UpdatedValue");
             cv.put("COL_C", i);
             argArray[0] = String.valueOf(id);
-            db.update("T1", cv, "_ID=?", argArray);
+            mDatabase.update("T1", cv, "_ID=?", argArray);
             i++;
             android.os.Trace.endSection();
         }
     }
 
-    /**
-     * This test measures a multi-threaded read-write environment where there are 2 readers and
-     * 1 writer in the database using WAL journal mode and NORMAL syncMode.
-     */
-    @Test
-    public void testMultithreadedReadWriteWithWalNormal() {
-        recreateTestDatabase(SQLiteDatabase.JOURNAL_MODE_WAL, SQLiteDatabase.SYNC_MODE_NORMAL);
-        insertT1TestDataSet();
-
-        performMultithreadedReadWriteTest();
-    }
-
     private void performMultithreadedReadWriteTest() {
         int totalBGIterations = 10000;
         // Writer - Fixed iterations to avoid consuming cycles from mainloop benchmark iterations
@@ -555,4 +553,3 @@
         createOrOpenTestDatabase(journalMode, syncMode);
     }
 }
-
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 9077d02..506d203 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -5747,7 +5747,7 @@
     method public void addOnCompleteListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.radio.ProgramList.OnCompleteListener);
     method public void addOnCompleteListener(@NonNull android.hardware.radio.ProgramList.OnCompleteListener);
     method public void close();
-    method @Deprecated @Nullable public android.hardware.radio.RadioManager.ProgramInfo get(@NonNull android.hardware.radio.ProgramSelector.Identifier);
+    method @Nullable public android.hardware.radio.RadioManager.ProgramInfo get(@NonNull android.hardware.radio.ProgramSelector.Identifier);
     method @FlaggedApi("android.hardware.radio.hd_radio_improved") @NonNull public java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramInfos(@NonNull android.hardware.radio.ProgramSelector.Identifier);
     method public void registerListCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.radio.ProgramList.ListCallback);
     method public void registerListCallback(@NonNull android.hardware.radio.ProgramList.ListCallback);
@@ -5799,7 +5799,7 @@
     field @Deprecated public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5; // 0x5
     field @Deprecated public static final int IDENTIFIER_TYPE_DAB_SID_EXT = 5; // 0x5
     field public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10; // 0xa
-    field @Deprecated public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; // 0xb
+    field public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; // 0xb
     field public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9; // 0x9
     field public static final int IDENTIFIER_TYPE_HD_STATION_ID_EXT = 3; // 0x3
     field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int IDENTIFIER_TYPE_HD_STATION_LOCATION = 15; // 0xf
@@ -5807,8 +5807,8 @@
     field @Deprecated public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4; // 0x4
     field public static final int IDENTIFIER_TYPE_INVALID = 0; // 0x0
     field public static final int IDENTIFIER_TYPE_RDS_PI = 2; // 0x2
-    field @Deprecated public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd
-    field @Deprecated public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc
+    field public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd
+    field public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc
     field public static final int IDENTIFIER_TYPE_VENDOR_END = 1999; // 0x7cf
     field @Deprecated public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = 1999; // 0x7cf
     field @Deprecated public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = 1000; // 0x3e8
@@ -5861,7 +5861,7 @@
     field public static final int CONFIG_DAB_DAB_SOFT_LINKING = 8; // 0x8
     field public static final int CONFIG_DAB_FM_LINKING = 7; // 0x7
     field public static final int CONFIG_DAB_FM_SOFT_LINKING = 9; // 0x9
-    field @Deprecated public static final int CONFIG_FORCE_ANALOG = 2; // 0x2
+    field public static final int CONFIG_FORCE_ANALOG = 2; // 0x2
     field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int CONFIG_FORCE_ANALOG_AM = 11; // 0xb
     field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int CONFIG_FORCE_ANALOG_FM = 10; // 0xa
     field public static final int CONFIG_FORCE_DIGITAL = 3; // 0x3
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index d2af9db..2e22071 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1882,6 +1882,7 @@
     method public void setRampingRingerEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public void setRs2Value(float);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setTestDeviceConnectionState(@NonNull android.media.AudioDeviceAttributes, boolean);
+    method @FlaggedApi("android.media.audio.focus_exclusive_with_recording") @RequiresPermission(android.Manifest.permission.QUERY_AUDIO_STATE) public boolean shouldNotificationSoundPlay(@NonNull android.media.AudioAttributes);
   }
 
   public static final class AudioRecord.MetricsConstants {
@@ -2343,6 +2344,9 @@
     field public static final int CPU_LOAD_RESET = 2; // 0x2
     field public static final int CPU_LOAD_RESUME = 3; // 0x3
     field public static final int CPU_LOAD_UP = 0; // 0x0
+    field @FlaggedApi("android.os.adpf_gpu_report_actual_work_duration") public static final int GPU_LOAD_DOWN = 6; // 0x6
+    field @FlaggedApi("android.os.adpf_gpu_report_actual_work_duration") public static final int GPU_LOAD_RESET = 7; // 0x7
+    field @FlaggedApi("android.os.adpf_gpu_report_actual_work_duration") public static final int GPU_LOAD_UP = 5; // 0x5
   }
 
   public final class PowerManager {
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index b4a6955..845a346 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -1311,8 +1311,9 @@
         if (!node.mEnded) {
             float durationScale = ValueAnimator.getDurationScale();
             durationScale = durationScale == 0  ? 1 : durationScale;
-            node.mEnded = node.mAnimation.pulseAnimationFrame(
-                    (long) (animPlayTime * durationScale));
+            if (node.mAnimation.pulseAnimationFrame((long) (animPlayTime * durationScale))) {
+                node.mEnded = true;
+            }
         }
     }
 
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index ee1d117b..d5eee63f 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -8099,7 +8099,7 @@
                         int end = data.indexOf('/', 14);
                         if (end < 0) {
                             // All we have is a package name.
-                            intent.mPackage = data.substring(14);
+                            intent.mPackage = Uri.decodeIfNeeded(data.substring(14));
                             if (!explicitAction) {
                                 intent.setAction(ACTION_MAIN);
                             }
@@ -8107,21 +8107,22 @@
                         } else {
                             // Target the Intent at the given package name always.
                             String authority = null;
-                            intent.mPackage = data.substring(14, end);
+                            intent.mPackage = Uri.decodeIfNeeded(data.substring(14, end));
                             int newEnd;
                             if ((end+1) < data.length()) {
                                 if ((newEnd=data.indexOf('/', end+1)) >= 0) {
                                     // Found a scheme, remember it.
-                                    scheme = data.substring(end+1, newEnd);
+                                    scheme = Uri.decodeIfNeeded(data.substring(end + 1, newEnd));
                                     end = newEnd;
                                     if (end < data.length() && (newEnd=data.indexOf('/', end+1)) >= 0) {
                                         // Found a authority, remember it.
-                                        authority = data.substring(end+1, newEnd);
+                                        authority = Uri.decodeIfNeeded(
+                                                data.substring(end + 1, newEnd));
                                         end = newEnd;
                                     }
                                 } else {
                                     // All we have is a scheme.
-                                    scheme = data.substring(end+1);
+                                    scheme = Uri.decodeIfNeeded(data.substring(end + 1));
                                 }
                             }
                             if (scheme == null) {
@@ -11762,27 +11763,33 @@
                         + this);
             }
             uri.append("android-app://");
-            uri.append(mPackage);
+            uri.append(Uri.encode(mPackage));
             String scheme = null;
             if (mData != null) {
-                scheme = mData.getScheme();
+                // All values here must be wrapped with Uri#encodeIfNotEncoded because it is
+                // possible to exploit the Uri API to return a raw unencoded value, which will
+                // not deserialize properly and may cause the resulting Intent to be transformed
+                // to a malicious value.
+                scheme = Uri.encodeIfNotEncoded(mData.getScheme(), null);
                 if (scheme != null) {
                     uri.append('/');
                     uri.append(scheme);
-                    String authority = mData.getEncodedAuthority();
+                    String authority = Uri.encodeIfNotEncoded(mData.getEncodedAuthority(), null);
                     if (authority != null) {
                         uri.append('/');
                         uri.append(authority);
-                        String path = mData.getEncodedPath();
+
+                        // Multiple path segments are allowed, don't encode the path / separator
+                        String path = Uri.encodeIfNotEncoded(mData.getEncodedPath(), "/");
                         if (path != null) {
                             uri.append(path);
                         }
-                        String queryParams = mData.getEncodedQuery();
+                        String queryParams = Uri.encodeIfNotEncoded(mData.getEncodedQuery(), null);
                         if (queryParams != null) {
                             uri.append('?');
                             uri.append(queryParams);
                         }
-                        String fragment = mData.getEncodedFragment();
+                        String fragment = Uri.encodeIfNotEncoded(mData.getEncodedFragment(), null);
                         if (fragment != null) {
                             uri.append('#');
                             uri.append(fragment);
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index f0a8996..a8dba51 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -382,10 +382,10 @@
      * start it unless initiated by a user interaction (typically launching its icon
      * from the launcher, could also include user actions like adding it as an app widget,
      * selecting it as a live wallpaper, selecting it as a keyboard, etc). Stopped
-     * applications will not receive broadcasts unless the sender specifies
+     * applications will not receive implicit broadcasts unless the sender specifies
      * {@link android.content.Intent#FLAG_INCLUDE_STOPPED_PACKAGES}.
      *
-     * <p>Applications should avoid launching activies, binding to or starting services, or
+     * <p>Applications should avoid launching activities, binding to or starting services, or
      * otherwise causing a stopped application to run unless initiated by the user.
      *
      * <p>An app can also return to the stopped state by a "force stop".
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 0e131b4..43322641 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -672,6 +672,13 @@
     public @interface UserActionReason {}
 
     /**
+     * The unarchival status is not set.
+     *
+     * @hide
+     */
+    public static final int UNARCHIVAL_STATUS_UNSET = -1;
+
+    /**
      * The unarchival is possible and will commence.
      *
      * <p> Note that this does not mean that the unarchival has completed. This status should be
@@ -736,6 +743,7 @@
      * @hide
      */
     @IntDef(value = {
+            UNARCHIVAL_STATUS_UNSET,
             UNARCHIVAL_OK,
             UNARCHIVAL_ERROR_USER_ACTION_NEEDED,
             UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE,
@@ -2696,8 +2704,6 @@
         public int developmentInstallFlags = 0;
         /** {@hide} */
         public int unarchiveId = -1;
-        /** {@hide} */
-        public IntentSender unarchiveIntentSender;
 
         private final ArrayMap<String, Integer> mPermissionStates;
 
@@ -2750,7 +2756,6 @@
             applicationEnabledSettingPersistent = source.readBoolean();
             developmentInstallFlags = source.readInt();
             unarchiveId = source.readInt();
-            unarchiveIntentSender = source.readParcelable(null, IntentSender.class);
         }
 
         /** {@hide} */
@@ -2785,7 +2790,6 @@
             ret.applicationEnabledSettingPersistent = applicationEnabledSettingPersistent;
             ret.developmentInstallFlags = developmentInstallFlags;
             ret.unarchiveId = unarchiveId;
-            ret.unarchiveIntentSender = unarchiveIntentSender;
             return ret;
         }
 
@@ -3495,7 +3499,6 @@
                     applicationEnabledSettingPersistent);
             pw.printHexPair("developmentInstallFlags", developmentInstallFlags);
             pw.printPair("unarchiveId", unarchiveId);
-            pw.printPair("unarchiveIntentSender", unarchiveIntentSender);
             pw.println();
         }
 
@@ -3540,7 +3543,6 @@
             dest.writeBoolean(applicationEnabledSettingPersistent);
             dest.writeInt(developmentInstallFlags);
             dest.writeInt(unarchiveId);
-            dest.writeParcelable(unarchiveIntentSender, flags);
         }
 
         public static final Parcelable.Creator<SessionParams>
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 0b60977..a2cd3e1 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -138,3 +138,11 @@
     description: "Add a new FGS type for media processing use cases."
     bug: "317788011"
 }
+
+flag {
+    name: "encode_app_intent"
+    namespace: "package_manager_service"
+    description: "Feature flag to encode app intent."
+    bug: "281848623"
+}
+
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index b96d832..ecffe9e 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -121,8 +121,12 @@
     // The native SQLiteConnection pointer.  (FOR INTERNAL USE ONLY)
     private long mConnectionPtr;
 
+    // Restrict this connection to read-only operations.
     private boolean mOnlyAllowReadOnlyOperations;
 
+    // Allow this connection to treat updates to temporary tables as read-only operations.
+    private boolean mAllowTempTableRetry = Flags.sqliteAllowTempTables();
+
     // The number of times attachCancellationSignal has been called.
     // Because SQLite statement execution can be reentrant, we keep track of how many
     // times we have attempted to attach a cancellation signal to the connection so that
@@ -142,6 +146,7 @@
     private static native void nativeFinalizeStatement(long connectionPtr, long statementPtr);
     private static native int nativeGetParameterCount(long connectionPtr, long statementPtr);
     private static native boolean nativeIsReadOnly(long connectionPtr, long statementPtr);
+    private static native boolean nativeUpdatesTempOnly(long connectionPtr, long statementPtr);
     private static native int nativeGetColumnCount(long connectionPtr, long statementPtr);
     private static native String nativeGetColumnName(long connectionPtr, long statementPtr,
             int index);
@@ -1097,7 +1102,7 @@
         try {
             final int numParameters = nativeGetParameterCount(mConnectionPtr, statementPtr);
             final int type = DatabaseUtils.getSqlStatementTypeExtended(sql);
-            final boolean readOnly = nativeIsReadOnly(mConnectionPtr, statementPtr);
+            boolean readOnly = nativeIsReadOnly(mConnectionPtr, statementPtr);
             statement = obtainPreparedStatement(sql, statementPtr, numParameters, type, readOnly,
                     seqNum);
             if (!skipCache && isCacheable(type)) {
@@ -1265,13 +1270,20 @@
 
     /**
      * Verify that the statement is read-only, if the connection only allows read-only
-     * operations.
+     * operations.  If the connection allows updates to temporary tables, then the statement is
+     * read-only if the only updates are to temporary tables.
      * @param statement The statement to check.
      * @throws SQLiteException if the statement could update the database inside a read-only
      * transaction.
      */
     void throwIfStatementForbidden(PreparedStatement statement) {
         if (mOnlyAllowReadOnlyOperations && !statement.mReadOnly) {
+            if (mAllowTempTableRetry) {
+                statement.mReadOnly =
+                        nativeUpdatesTempOnly(mConnectionPtr, statement.mStatementPtr);
+                if (statement.mReadOnly) return;
+            }
+
             throw new SQLiteException("Cannot execute this statement because it "
                     + "might modify the database but the connection is read-only.");
         }
diff --git a/core/java/android/database/sqlite/flags.aconfig b/core/java/android/database/sqlite/flags.aconfig
index 62a5123..92ef9c2 100644
--- a/core/java/android/database/sqlite/flags.aconfig
+++ b/core/java/android/database/sqlite/flags.aconfig
@@ -7,3 +7,11 @@
      description: "SQLite APIs held back for Android 15"
      bug: "279043253"
 }
+
+flag {
+     name: "sqlite_allow_temp_tables"
+     namespace: "system_performance"
+     is_fixed_read_only: true
+     description: "Permit updates to TEMP tables in read-only transactions"
+     bug: "317993835"
+}
diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java
index c5167db..a3a2a2e 100644
--- a/core/java/android/hardware/radio/ProgramList.java
+++ b/core/java/android/hardware/radio/ProgramList.java
@@ -304,11 +304,7 @@
      *
      * @param id primary identifier of a program to fetch
      * @return the program info, or null if there is no such program on the list
-     *
-     * @deprecated Use {@link #getProgramInfos(ProgramSelector.Identifier)} to get all programs
-     * with the given primary identifier
      */
-    @Deprecated
     public @Nullable RadioManager.ProgramInfo get(@NonNull ProgramSelector.Identifier id) {
         Map<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries;
         synchronized (mLock) {
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index 7e5c141..4c95e02 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -312,20 +312,14 @@
     public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10;
     /**
      * 1: AM, 2:FM
-     * @deprecated use {@link #IDENTIFIER_TYPE_DRMO_FREQUENCY} instead
      */
-    @Deprecated
     public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11;
     /**
      * 32bit primary identifier for SiriusXM Satellite Radio.
-     *
-     * @deprecated SiriusXM Satellite Radio is not supported
      */
     public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12;
     /**
      * 0-999 range
-     *
-     * @deprecated SiriusXM Satellite Radio is not supported
      */
     public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13;
     /**
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index f0f7e8a..41f21ef 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -166,12 +166,7 @@
      * analog handover state managed from the HAL implementation side.
      *
      * <p>Some radio technologies may not support this, i.e. DAB.
-     *
-     * @deprecated Use {@link #CONFIG_FORCE_ANALOG_FM} instead. If {@link #CONFIG_FORCE_ANALOG_FM}
-     * is supported in HAL, {@link RadioTuner#setConfigFlag} and {@link RadioTuner#isConfigFlagSet}
-     * with CONFIG_FORCE_ANALOG will set/get the value of {@link #CONFIG_FORCE_ANALOG_FM}.
      */
-    @Deprecated
     public static final int CONFIG_FORCE_ANALOG = 2;
     /**
      * Forces the digital playback for the supporting radio technology.
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java
index 70de477..05a3e18 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -21,6 +21,7 @@
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Intent;
+import android.content.pm.Flags;
 import android.os.Environment;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -1971,6 +1972,42 @@
     }
 
     /**
+     * Encodes a value it wasn't already encoded.
+     *
+     * @param value string to encode
+     * @param allow characters to allow
+     * @return encoded value
+     * @hide
+     */
+    public static String encodeIfNotEncoded(@Nullable String value, @Nullable String allow) {
+        if (value == null) return null;
+        if (!Flags.encodeAppIntent() || isEncoded(value, allow)) return value;
+        return encode(value, allow);
+    }
+
+    /**
+     * Returns true if the given string is already encoded to safe characters.
+     *
+     * @param value string to check
+     * @param allow characters to allow
+     * @return true if the string is already encoded or false if it should be encoded
+     */
+    private static boolean isEncoded(@Nullable String value, @Nullable String allow) {
+        if (value == null) return true;
+        for (int index = 0; index < value.length(); index++) {
+            char c = value.charAt(index);
+
+            // Allow % because that's the prefix for an encoded character. This method will fail
+            // for decoded strings whose onlyinvalid character is %, but it's assumed that % alone
+            // cannot cause malicious behavior in the framework.
+            if (!isAllowed(c, allow) && c != '%') {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
      * Decodes '%'-escaped octets in the given string using the UTF-8 scheme.
      * Replaces invalid octets with the unicode replacement character
      * ("\\uFFFD").
@@ -1988,6 +2025,18 @@
     }
 
     /**
+     * Decodes a string if it was encoded, indicated by containing a %.
+     * @param value encoded string to decode
+     * @return decoded value
+     * @hide
+     */
+    public static String decodeIfNeeded(@Nullable String value) {
+        if (value == null) return null;
+        if (Flags.encodeAppIntent() && value.contains("%")) return decode(value);
+        return value;
+    }
+
+    /**
      * Support for part implementations.
      */
     static abstract class AbstractPart {
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 5a40e42..8219d2f 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -1802,7 +1802,7 @@
      * Use {@link #FLAG_LISTEN_DISABLE} to disable listening.
      * Also refer to {@link #resetDiscoveryTechnology(Activity)} to restore these changes.
      * </p>
-     * The pollTech, listenTech parameters can be one or several of below list.
+     * The pollTechnology, listenTechnology parameters can be one or several of below list.
      * <pre>
      *                    Poll                    Listen
      *  Passive A         0x01   (NFC_A)           0x01  (NFC_PASSIVE_A)
@@ -1820,25 +1820,25 @@
      *         NfcAdapter.FLAG_READER_DISABLE, NfcAdapter.FLAG_LISTEN_KEEP);
      * }</pre></p>
      * @param activity The Activity that requests NFC controller to enable specific technologies.
-     * @param pollTech Flags indicating poll technologies.
-     * @param listenTech Flags indicating listen technologies.
+     * @param pollTechnology Flags indicating poll technologies.
+     * @param listenTechnology Flags indicating listen technologies.
      * @throws UnsupportedOperationException if FEATURE_NFC,
      * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF are unavailable.
      */
 
     @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
     public void setDiscoveryTechnology(@NonNull Activity activity,
-            @PollTechnology int pollTech, @ListenTechnology int listenTech) {
-        if (listenTech == FLAG_LISTEN_DISABLE) {
+            @PollTechnology int pollTechnology, @ListenTechnology int listenTechnology) {
+        if (listenTechnology == FLAG_LISTEN_DISABLE) {
             synchronized (sLock) {
                 if (!sHasNfcFeature) {
                     throw new UnsupportedOperationException();
                 }
             }
-            mNfcActivityManager.enableReaderMode(activity, null, pollTech, null);
+            mNfcActivityManager.enableReaderMode(activity, null, pollTechnology, null);
             return;
         }
-        if (pollTech == FLAG_READER_DISABLE) {
+        if (pollTechnology == FLAG_READER_DISABLE) {
             synchronized (sLock) {
                 if (!sHasCeFeature) {
                     throw new UnsupportedOperationException();
@@ -1851,7 +1851,7 @@
                 }
             }
         }
-        mNfcActivityManager.setDiscoveryTech(activity, pollTech, listenTech);
+        mNfcActivityManager.setDiscoveryTech(activity, pollTechnology, listenTechnology);
     }
 
     /**
diff --git a/core/java/android/os/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java
index e005910..37bde3d 100644
--- a/core/java/android/os/PerformanceHintManager.java
+++ b/core/java/android/os/PerformanceHintManager.java
@@ -149,13 +149,47 @@
         @TestApi
         public static final int CPU_LOAD_RESUME = 3;
 
+        /**
+         * This hint indicates an increase in GPU workload intensity. It means that
+         * this hint session needs extra GPU resources to meet the target duration.
+         * This hint must be sent before reporting the actual duration to the session.
+         *
+         * @hide
+         */
+        @TestApi
+        @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION)
+        public static final int GPU_LOAD_UP = 5;
+
+        /**
+         * This hint indicates a decrease in GPU workload intensity. It means that
+         * this hint session can reduce GPU resources and still meet the target duration.
+         *
+         * @hide
+         */
+        @TestApi
+        @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION)
+        public static final int GPU_LOAD_DOWN = 6;
+
+        /**
+        * This hint indicates an upcoming GPU workload that is completely changed and
+        * unknown. It means that the hint session should reset GPU resources to a known
+        * baseline to prepare for an arbitrary load, and must wake up if inactive.
+         *
+         * @hide
+         */
+        @TestApi
+        @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION)
+        public static final int GPU_LOAD_RESET = 7;
+
         /** @hide */
         @Retention(RetentionPolicy.SOURCE)
         @IntDef(prefix = {"CPU_LOAD_"}, value = {
             CPU_LOAD_UP,
             CPU_LOAD_DOWN,
             CPU_LOAD_RESET,
-            CPU_LOAD_RESUME
+            CPU_LOAD_RESUME,
+            GPU_LOAD_UP,
+            GPU_LOAD_DOWN
         })
         public @interface Hint {}
 
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index c14810b..f3496e7 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -425,6 +425,8 @@
                 throw new ZygoteStartFailedEx("Embedded newlines not allowed");
             } else if (arg.indexOf('\r') >= 0) {
                 throw new ZygoteStartFailedEx("Embedded carriage returns not allowed");
+            } else if (arg.indexOf('\u0000') >= 0) {
+                throw new ZygoteStartFailedEx("Embedded nulls not allowed");
             }
         }
 
@@ -965,6 +967,14 @@
             return true;
         }
 
+        for (/* NonNull */ String s : mApiDenylistExemptions) {
+            // indexOf() is intrinsified and faster than contains().
+            if (s.indexOf('\n') >= 0 || s.indexOf('\r') >= 0 || s.indexOf('\u0000') >= 0) {
+                Slog.e(LOG_TAG, "Failed to set API denylist exemptions: Bad character");
+                mApiDenylistExemptions = Collections.emptyList();
+                return false;
+            }
+        }
         try {
             state.mZygoteOutputWriter.write(Integer.toString(mApiDenylistExemptions.size() + 1));
             state.mZygoteOutputWriter.newLine();
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index 3ecf74e..54ed73c 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -134,16 +134,16 @@
     @EnforcePermission("MOUNT_UNMOUNT_FILESYSTEMS")
     void setDebugFlags(int flags, int mask) = 60;
     @EnforcePermission("STORAGE_INTERNAL")
-    void createUserStorageKeys(int userId, int serialNumber, boolean ephemeral) = 61;
+    void createUserStorageKeys(int userId, boolean ephemeral) = 61;
     @EnforcePermission("STORAGE_INTERNAL")
     void destroyUserStorageKeys(int userId) = 62;
     @EnforcePermission("STORAGE_INTERNAL")
-    void unlockCeStorage(int userId, int serialNumber, in byte[] secret) = 63;
+    void unlockCeStorage(int userId, in byte[] secret) = 63;
     @EnforcePermission("STORAGE_INTERNAL")
     void lockCeStorage(int userId) = 64;
     boolean isCeStorageUnlocked(int userId) = 65;
     @EnforcePermission("STORAGE_INTERNAL")
-    void prepareUserStorage(in String volumeUuid, int userId, int serialNumber, int flags) = 66;
+    void prepareUserStorage(in String volumeUuid, int userId, int flags) = 66;
     @EnforcePermission("STORAGE_INTERNAL")
     void destroyUserStorage(in String volumeUuid, int userId, int flags) = 67;
     @EnforcePermission("STORAGE_INTERNAL")
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 78a12f7..9587db1 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -1602,14 +1602,13 @@
      * This is only intended to be called by UserManagerService, as part of creating a user.
      *
      * @param userId ID of the user
-     * @param serialNumber serial number of the user
      * @param ephemeral whether the user is ephemeral
      * @throws RuntimeException on error.  The user's keys already existing is considered an error.
      * @hide
      */
-    public void createUserStorageKeys(int userId, int serialNumber, boolean ephemeral) {
+    public void createUserStorageKeys(int userId, boolean ephemeral) {
         try {
-            mStorageManager.createUserStorageKeys(userId, serialNumber, ephemeral);
+            mStorageManager.createUserStorageKeys(userId, ephemeral);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1653,9 +1652,9 @@
     }
 
     /** {@hide} */
-    public void prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags) {
+    public void prepareUserStorage(String volumeUuid, int userId, int flags) {
         try {
-            mStorageManager.prepareUserStorage(volumeUuid, userId, serialNumber, flags);
+            mStorageManager.prepareUserStorage(volumeUuid, userId, flags);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 144e64f..e8da0d9 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3603,12 +3603,17 @@
                                     + " type:" + mUri.getPath()
                                     + " in package:" + cr.getPackageName());
                         }
+                        // When a generation number changes, remove cached values, remove the old
+                        // generation tracker and request a new one
+                        generationTracker.destroy();
+                        mGenerationTrackers.remove(prefix);
                         for (int i = mValues.size() - 1; i >= 0; i--) {
                             String key = mValues.keyAt(i);
                             if (key.startsWith(prefix)) {
                                 mValues.remove(key);
                             }
                         }
+                        needsGenerationTracker = true;
                     } else {
                         boolean prefixCached = mValues.containsKey(prefix);
                         if (prefixCached) {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index b4ac9a2..d8fa415 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1260,7 +1260,7 @@
      * <p>When this compat override is enabled the min aspect ratio given in the app's manifest can
      * be overridden by the device manufacturer using their discretion to improve display
      * compatibility unless the app's manifest value is higher. This treatment will also apply if
-     * no min aspect ratio value is provided in the manifest. These treatments can apply only in
+     * no min aspect ratio value is provided in the manifest. These treatments can apply either in
      * specific cases (e.g. device is in portrait) or each time the app is displayed on screen.
      *
      * <p>Setting this property to {@code false} informs the system that the app must be
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index bb49679..dbeffc8 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -1469,7 +1469,8 @@
         if (infos.size() == 0) {
             throw new IllegalArgumentException("No VirtualViewInfo found");
         }
-        if (isCredmanRequested(view) && mIsFillAndSaveDialogDisabledForCredentialManager) {
+        if (shouldSuppressDialogsForCredman(view)
+                && mIsFillAndSaveDialogDisabledForCredentialManager) {
             if (sDebug) {
                 Log.d(TAG, "Ignoring Fill Dialog request since important for credMan:"
                         + view.getAutofillId().toString());
@@ -1493,7 +1494,7 @@
      * @hide
      */
     public void notifyViewEnteredForFillDialog(View v) {
-        if (isCredmanRequested(v)
+        if (shouldSuppressDialogsForCredman(v)
                 && mIsFillAndSaveDialogDisabledForCredentialManager) {
             if (sDebug) {
                 Log.d(TAG, "Ignoring Fill Dialog request since important for credMan:"
@@ -3390,19 +3391,39 @@
         }
     }
 
-    private boolean isCredmanRequested(View view) {
+    private boolean shouldSuppressDialogsForCredman(View view) {
         if (view == null) {
             return false;
         }
+        // isCredential field indicates that the developer might be calling Credman, and we should
+        // suppress autofill dialogs. But it is not a good enough indicator that there is a valid
+        // credman option.
         if (view.isCredential()) {
             return true;
         }
+        return containsAutofillHintPrefix(view, View.AUTOFILL_HINT_CREDENTIAL_MANAGER);
+    }
+
+    private boolean isCredmanRequested(View view) {
+        if (view == null) {
+            return false;
+        }
         String[] hints = view.getAutofillHints();
         if (hints == null) {
             return false;
         }
+        // if hint starts with 'credential=', then we assume that there is a valid
+        // credential option set by the client.
+        return containsAutofillHintPrefix(view, View.AUTOFILL_HINT_CREDENTIAL_MANAGER + "=");
+    }
+
+    private boolean containsAutofillHintPrefix(View view, String prefix) {
+        String[] hints = view.getAutofillHints();
+        if (hints == null) {
+            return false;
+        }
         for (String hint : hints) {
-            if (hint != null && hint.startsWith(View.AUTOFILL_HINT_CREDENTIAL_MANAGER)) {
+            if (hint != null && hint.startsWith(prefix)) {
                 return true;
             }
         }
diff --git a/core/java/android/window/TransitionFilter.java b/core/java/android/window/TransitionFilter.java
index e62d5c9..64fe66e 100644
--- a/core/java/android/window/TransitionFilter.java
+++ b/core/java/android/window/TransitionFilter.java
@@ -212,7 +212,9 @@
                         continue;
                     }
                 }
-                if (!matchesTopActivity(change.getTaskInfo())) continue;
+                if (!matchesTopActivity(change.getTaskInfo(), change.getActivityComponent())) {
+                    continue;
+                }
                 if (mModes != null) {
                     boolean pass = false;
                     for (int m = 0; m < mModes.length; ++m) {
@@ -234,11 +236,15 @@
             return false;
         }
 
-        private boolean matchesTopActivity(ActivityManager.RunningTaskInfo info) {
+        private boolean matchesTopActivity(ActivityManager.RunningTaskInfo taskInfo,
+                @Nullable ComponentName activityComponent) {
             if (mTopActivity == null) return true;
-            if (info == null) return false;
-            final ComponentName component = info.topActivity;
-            return mTopActivity.equals(component);
+            if (activityComponent != null) {
+                return mTopActivity.equals(activityComponent);
+            } else if (taskInfo != null) {
+                return mTopActivity.equals(taskInfo.topActivity);
+            }
+            return false;
         }
 
         /** Check if the request matches this filter. It may generate false positives */
@@ -247,7 +253,7 @@
             if (mActivityType == ACTIVITY_TYPE_UNDEFINED) return true;
             return request.getTriggerTask() != null
                     && request.getTriggerTask().getActivityType() == mActivityType
-                    && matchesTopActivity(request.getTriggerTask());
+                    && matchesTopActivity(request.getTriggerTask(), null /* activityCmp */);
         }
 
         @Override
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 7c9340e..bceb872 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -44,6 +44,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.content.ComponentName;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.HardwareBuffer;
@@ -635,6 +636,7 @@
         private @ColorInt int mBackgroundColor;
         private SurfaceControl mSnapshot = null;
         private float mSnapshotLuma;
+        private ComponentName mActivityComponent = null;
 
         public Change(@Nullable WindowContainerToken container, @NonNull SurfaceControl leash) {
             mContainer = container;
@@ -663,6 +665,7 @@
             mBackgroundColor = in.readInt();
             mSnapshot = in.readTypedObject(SurfaceControl.CREATOR);
             mSnapshotLuma = in.readFloat();
+            mActivityComponent = in.readTypedObject(ComponentName.CREATOR);
         }
 
         private Change localRemoteCopy() {
@@ -685,6 +688,7 @@
             out.mBackgroundColor = mBackgroundColor;
             out.mSnapshot = mSnapshot != null ? new SurfaceControl(mSnapshot, "localRemote") : null;
             out.mSnapshotLuma = mSnapshotLuma;
+            out.mActivityComponent = mActivityComponent;
             return out;
         }
 
@@ -780,6 +784,11 @@
             mSnapshotLuma = luma;
         }
 
+        /** Sets the component-name of the container. Container must be an Activity. */
+        public void setActivityComponent(@Nullable ComponentName component) {
+            mActivityComponent = component;
+        }
+
         /** @return the container that is changing. May be null if non-remotable (eg. activity) */
         @Nullable
         public WindowContainerToken getContainer() {
@@ -913,6 +922,12 @@
             return mSnapshotLuma;
         }
 
+        /** @return the component-name of this container (if it is an activity). */
+        @Nullable
+        public ComponentName getActivityComponent() {
+            return mActivityComponent;
+        }
+
         /** @hide */
         @Override
         public void writeToParcel(@NonNull Parcel dest, int flags) {
@@ -936,6 +951,7 @@
             dest.writeInt(mBackgroundColor);
             dest.writeTypedObject(mSnapshot, flags);
             dest.writeFloat(mSnapshotLuma);
+            dest.writeTypedObject(mActivityComponent, flags);
         }
 
         @NonNull
@@ -994,6 +1010,10 @@
             if (mLastParent != null) {
                 sb.append(" lastParent="); sb.append(mLastParent);
             }
+            if (mActivityComponent != null) {
+                sb.append(" component=");
+                sb.append(mActivityComponent.flattenToShortString());
+            }
             sb.append('}');
             return sb.toString();
         }
diff --git a/core/jni/android_database_SQLiteConnection.cpp b/core/jni/android_database_SQLiteConnection.cpp
index 893cc98..6f1c763 100644
--- a/core/jni/android_database_SQLiteConnection.cpp
+++ b/core/jni/android_database_SQLiteConnection.cpp
@@ -82,10 +82,16 @@
     const String8 path;
     const String8 label;
 
+    // The prepared statement used to determine which tables are updated by a statement.  This
+    // is is initially null.  It is set non-null on first use.
+    sqlite3_stmt* tableQuery;
+
     volatile bool canceled;
 
     SQLiteConnection(sqlite3* db, int openFlags, const String8& path, const String8& label) :
-        db(db), openFlags(openFlags), path(path), label(label), canceled(false) { }
+            db(db), openFlags(openFlags), path(path), label(label), tableQuery(nullptr),
+            canceled(false) { }
+
 };
 
 // Called each time a statement begins execution, when tracing is enabled.
@@ -188,6 +194,9 @@
 
     if (connection) {
         ALOGV("Closing connection %p", connection->db);
+        if (connection->tableQuery != nullptr) {
+            sqlite3_finalize(connection->tableQuery);
+        }
         int err = sqlite3_close(connection->db);
         if (err != SQLITE_OK) {
             // This can happen if sub-objects aren't closed first.  Make sure the caller knows.
@@ -419,6 +428,46 @@
     return sqlite3_stmt_readonly(statement) != 0;
 }
 
+static jboolean nativeUpdatesTempOnly(JNIEnv* env, jclass,
+        jlong connectionPtr, jlong statementPtr) {
+    sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
+    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+
+    int result = SQLITE_OK;
+    if (connection->tableQuery == nullptr) {
+        static char const* sql =
+                "SELECT COUNT(*) FROM tables_used(?) WHERE schema != 'temp' AND wr != 0";
+        result = sqlite3_prepare_v2(connection->db, sql, -1, &connection->tableQuery, nullptr);
+        if (result != SQLITE_OK) {
+            ALOGE("failed to compile query table: %s",
+                  sqlite3_errstr(sqlite3_extended_errcode(connection->db)));
+            return false;
+        }
+    }
+
+    // A temporary, to simplify the code.
+    sqlite3_stmt* query = connection->tableQuery;
+    sqlite3_reset(query);
+    sqlite3_clear_bindings(query);
+    result = sqlite3_bind_text(query, 1, sqlite3_sql(statement), -1, SQLITE_STATIC);
+    if (result != SQLITE_OK) {
+        ALOGE("tables bind pointer returns %s", sqlite3_errstr(result));
+        return false;
+    }
+    result = sqlite3_step(query);
+    if (result != SQLITE_ROW) {
+        ALOGE("tables query error: %d/%s", result, sqlite3_errstr(result));
+        // Make sure the query is no longer bound to the statement.
+        sqlite3_clear_bindings(query);
+        return false;
+    }
+
+    int count = sqlite3_column_int(query, 0);
+    // Make sure the query is no longer bound to the statement SQL string.
+    sqlite3_clear_bindings(query);
+    return count == 0;
+}
+
 static jint nativeGetColumnCount(JNIEnv* env, jclass clazz, jlong connectionPtr,
         jlong statementPtr) {
     sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
@@ -915,6 +964,8 @@
             (void*)nativeGetParameterCount },
     { "nativeIsReadOnly", "(JJ)Z",
             (void*)nativeIsReadOnly },
+    { "nativeUpdatesTempOnly", "(JJ)Z",
+            (void*)nativeUpdatesTempOnly },
     { "nativeGetColumnCount", "(JJ)I",
             (void*)nativeGetColumnCount },
     { "nativeGetColumnName", "(JJI)Ljava/lang/String;",
diff --git a/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java b/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java
index 43266a5..cb3f99c 100644
--- a/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java
+++ b/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java
@@ -17,6 +17,7 @@
 package android.animation;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import android.util.PollingCheck;
@@ -343,6 +344,20 @@
     }
 
     @Test
+    public void childAnimatorCancelsDuringUpdate_animatorSetIsEnded() throws Throwable {
+        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                animation.cancel();
+            }
+        });
+        mActivity.runOnUiThread(() -> {
+            mSet1.start();
+            assertFalse(mSet1.isRunning());
+        });
+    }
+
+    @Test
     public void reentrantStart() throws Throwable {
         CountDownLatch latch = new CountDownLatch(3);
         AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
index 3fc08ee..bd5f809 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
@@ -26,6 +26,9 @@
 import android.database.Cursor;
 import android.database.DatabaseUtils;
 import android.os.SystemClock;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
@@ -35,6 +38,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -53,6 +57,10 @@
 @SmallTest
 public class SQLiteDatabaseTest {
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
     private static final String TAG = "SQLiteDatabaseTest";
 
     private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
@@ -347,4 +355,50 @@
 
         assertTrue("ReadThread failed with errors: " + errors, errors.isEmpty());
     }
+
+    @RequiresFlagsEnabled(Flags.FLAG_SQLITE_ALLOW_TEMP_TABLES)
+    @Test
+    public void testTempTable() {
+        boolean allowed;
+        allowed = true;
+        mDatabase.beginTransactionReadOnly();
+        try {
+            mDatabase.execSQL("CREATE TEMP TABLE t1 (i int, j int);");
+            mDatabase.execSQL("INSERT INTO t1 (i, j) VALUES (2, 20)");
+            mDatabase.execSQL("INSERT INTO t1 (i, j) VALUES (3, 30)");
+
+            final String sql = "SELECT i FROM t1 WHERE j = 30";
+            try (SQLiteRawStatement s = mDatabase.createRawStatement(sql)) {
+                assertTrue(s.step());
+                assertEquals(3, s.getColumnInt(0));
+            }
+
+        } catch (SQLiteException e) {
+            allowed = false;
+        } finally {
+            mDatabase.endTransaction();
+        }
+        assertTrue(allowed);
+
+        // Repeat the test on the main schema.
+        allowed = true;
+        mDatabase.beginTransactionReadOnly();
+        try {
+            mDatabase.execSQL("CREATE TABLE t2 (i int, j int);");
+            mDatabase.execSQL("INSERT INTO t2 (i, j) VALUES (2, 20)");
+            mDatabase.execSQL("INSERT INTO t2 (i, j) VALUES (3, 30)");
+
+            final String sql = "SELECT i FROM t2 WHERE j = 30";
+            try (SQLiteRawStatement s = mDatabase.createRawStatement(sql)) {
+                assertTrue(s.step());
+                assertEquals(3, s.getColumnInt(0));
+            }
+
+        } catch (SQLiteException e) {
+            allowed = false;
+        } finally {
+            mDatabase.endTransaction();
+        }
+        assertFalse(allowed);
+    }
 }
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml
index 681a52b..e04ab81 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml
@@ -21,4 +21,9 @@
     android:orientation="vertical"
     android:id="@+id/bubble_bar_expanded_view">
 
+    <com.android.wm.shell.bubbles.bar.BubbleBarHandleView
+        android:id="@+id/bubble_bar_handle_view"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content" />
+
 </com.android.wm.shell.bubbles.bar.BubbleBarExpandedView>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index d073f1d..66c0c96 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -70,7 +70,7 @@
     private @Nullable Supplier<Rect> mLayerBoundsSupplier;
     private @Nullable Listener mListener;
 
-    private BubbleBarHandleView mHandleView = new BubbleBarHandleView(getContext());
+    private BubbleBarHandleView mHandleView;
     private @Nullable TaskView mTaskView;
     private @Nullable BubbleOverflowContainerView mOverflowView;
 
@@ -111,7 +111,7 @@
         setElevation(getResources().getDimensionPixelSize(R.dimen.bubble_elevation));
         mCaptionHeight = context.getResources().getDimensionPixelSize(
                 R.dimen.bubble_bar_expanded_view_caption_height);
-        addView(mHandleView);
+        mHandleView = findViewById(R.id.bubble_bar_handle_view);
         applyThemeAttrs();
         setClipToOutline(true);
         setOutlineProvider(new ViewOutlineProvider() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 7dec12a..bc1a575 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -388,6 +388,7 @@
 
     IBinder startResizeTransition(WindowContainerTransaction wct,
             Transitions.TransitionHandler handler,
+            @Nullable TransitionConsumedCallback consumedCallback,
             @Nullable TransitionFinishedCallback finishCallback) {
         if (mPendingResize != null) {
             mPendingResize.cancel(null);
@@ -396,13 +397,14 @@
         }
 
         IBinder transition = mTransitions.startTransition(TRANSIT_CHANGE, wct, handler);
-        setResizeTransition(transition, finishCallback);
+        setResizeTransition(transition, consumedCallback, finishCallback);
         return transition;
     }
 
     void setResizeTransition(@NonNull IBinder transition,
+            @Nullable TransitionConsumedCallback consumedCallback,
             @Nullable TransitionFinishedCallback finishCallback) {
-        mPendingResize = new TransitSession(transition, null /* consumedCb */, finishCallback);
+        mPendingResize = new TransitSession(transition, consumedCallback, finishCallback);
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  splitTransition "
                 + " deduced Resize split screen");
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 96e57e7..0781a9e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -2236,8 +2236,11 @@
         sendOnBoundsChanged();
         if (ENABLE_SHELL_TRANSITIONS) {
             mSplitLayout.setDividerInteractive(false, false, "onSplitResizeStart");
-            mSplitTransitions.startResizeTransition(wct, this, (finishWct, t) ->
-                    mSplitLayout.setDividerInteractive(true, false, "onSplitResizeFinish"));
+            mSplitTransitions.startResizeTransition(wct, this, (aborted) -> {
+                mSplitLayout.setDividerInteractive(true, false, "onSplitResizeConsumed");
+            }, (finishWct, t) -> {
+                mSplitLayout.setDividerInteractive(true, false, "onSplitResizeFinish");
+            });
         } else {
             // Only need screenshot for legacy case because shell transition should screenshot
             // itself during transition.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index e22bf3d..e9da258 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -64,6 +64,7 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.IApplicationThread;
 import android.app.PendingIntent;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Binder;
@@ -420,6 +421,30 @@
     }
 
     @Test
+    public void testTransitionFilterActivityComponent() {
+        TransitionFilter filter = new TransitionFilter();
+        ComponentName cmpt = new ComponentName("testpak", "testcls");
+        filter.mRequirements =
+                new TransitionFilter.Requirement[]{new TransitionFilter.Requirement()};
+        filter.mRequirements[0].mTopActivity = cmpt;
+        filter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
+
+        final RunningTaskInfo taskInf = createTaskInfo(1);
+        final TransitionInfo openTask = new TransitionInfoBuilder(TRANSIT_OPEN)
+                .addChange(TRANSIT_OPEN, taskInf).build();
+        assertFalse(filter.matches(openTask));
+
+        taskInf.topActivity = cmpt;
+        final TransitionInfo openTaskCmpt = new TransitionInfoBuilder(TRANSIT_OPEN)
+                .addChange(TRANSIT_OPEN, taskInf).build();
+        assertTrue(filter.matches(openTaskCmpt));
+
+        final TransitionInfo openAct = new TransitionInfoBuilder(TRANSIT_OPEN)
+                .addChange(TRANSIT_OPEN, cmpt).build();
+        assertTrue(filter.matches(openAct));
+    }
+
+    @Test
     public void testRegisteredRemoteTransition() {
         Transitions transitions = createTestTransitions();
         transitions.replaceDefaultHandlerForTest(mDefaultHandler);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionInfoBuilder.java
index 8343858..b8939e6f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionInfoBuilder.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionInfoBuilder.java
@@ -21,6 +21,7 @@
 import static org.mockito.Mockito.mock;
 
 import android.app.ActivityManager;
+import android.content.ComponentName;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.window.TransitionInfo;
@@ -50,20 +51,34 @@
     }
 
     public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
-            @TransitionInfo.ChangeFlags int flags, ActivityManager.RunningTaskInfo taskInfo) {
+            @TransitionInfo.ChangeFlags int flags, ActivityManager.RunningTaskInfo taskInfo,
+            ComponentName activityComponent) {
         final TransitionInfo.Change change = new TransitionInfo.Change(
                 taskInfo != null ? taskInfo.token : null, createMockSurface(true /* valid */));
         change.setMode(mode);
         change.setFlags(flags);
         change.setTaskInfo(taskInfo);
+        change.setActivityComponent(activityComponent);
         return addChange(change);
     }
 
+    /** Add a change to the TransitionInfo */
+    public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
+            @TransitionInfo.ChangeFlags int flags, ActivityManager.RunningTaskInfo taskInfo) {
+        return addChange(mode, flags, taskInfo, null /* activityComponent */);
+    }
+
     public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
             ActivityManager.RunningTaskInfo taskInfo) {
         return addChange(mode, TransitionInfo.FLAG_NONE, taskInfo);
     }
 
+    /** Add a change to the TransitionInfo */
+    public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
+            ComponentName activityComponent) {
+        return addChange(mode, TransitionInfo.FLAG_NONE, null /* taskinfo */, activityComponent);
+    }
+
     public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode) {
         return addChange(mode, TransitionInfo.FLAG_NONE, null /* taskInfo */);
     }
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index a5a69f9..4918289 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -21,6 +21,7 @@
 import static android.content.Context.DEVICE_ID_DEFAULT;
 import static android.media.audio.Flags.autoPublicVolumeApiHardening;
 import static android.media.audio.Flags.automaticBtDeviceType;
+import static android.media.audio.Flags.FLAG_FOCUS_EXCLUSIVE_WITH_RECORDING;
 import static android.media.audio.Flags.FLAG_FOCUS_FREEZE_TEST_API;
 import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
 
@@ -10081,6 +10082,28 @@
         }
     }
 
+    /**
+     * @hide
+     * Checks whether a notification sound should be played or not, as reported by the state
+     * of the audio framework. Querying whether playback should proceed is favored over
+     * playing and letting the sound be muted or not.
+     * @param aa the {@link AudioAttributes} of the notification about to maybe play
+     * @return true if the audio framework state is such that the notification should be played
+     *    because at time of checking, and the notification will be heard,
+     *    false otherwise
+     */
+    @TestApi
+    @FlaggedApi(FLAG_FOCUS_EXCLUSIVE_WITH_RECORDING)
+    @RequiresPermission(android.Manifest.permission.QUERY_AUDIO_STATE)
+    public boolean shouldNotificationSoundPlay(@NonNull final AudioAttributes aa) {
+        final IAudioService service = getService();
+        try {
+            return service.shouldNotificationSoundPlay(Objects.requireNonNull(aa));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     //====================================================================
     // Mute await connection
 
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 5c268d4..2eec9b3 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -775,4 +775,8 @@
     @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)")
     FadeManagerConfiguration getFadeManagerConfigurationForFocusLoss();
+
+    @EnforcePermission("QUERY_AUDIO_STATE")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.QUERY_AUDIO_STATE)")
+    boolean shouldNotificationSoundPlay(in AudioAttributes aa);
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 8ac364e7..b2c23a4 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -69,7 +69,8 @@
     companion object {
         private const val TAG = "CredAutofill"
 
-        private const val SESSION_ID_KEY = "session_id"
+        private const val SESSION_ID_KEY = "autofill_session_id"
+        private const val REQUEST_ID_KEY = "autofill_request_id"
         private const val CRED_HINT_PREFIX = "credential="
         private const val REQUEST_DATA_KEY = "requestData"
         private const val CANDIDATE_DATA_KEY = "candidateQueryData"
@@ -97,16 +98,23 @@
         val callingPackage = structure.activityComponent.packageName
         Log.i(TAG, "onFillCredentialRequest called for $callingPackage")
 
-        var sessionId = request.clientState?.getInt(SESSION_ID_KEY)
-
-        Log.i(TAG, "Autofill sessionId: " + sessionId)
-        if (sessionId == null) {
-            Log.i(TAG, "Session Id not found")
-            callback.onFailure("Session Id not found")
+        val clientState = request.clientState
+        if (clientState == null) {
+            Log.i(TAG, "Client state not found")
+            callback.onFailure("Client state not found")
+            return
+        }
+        val sessionId = clientState.getInt(SESSION_ID_KEY)
+        val requestId = clientState.getInt(REQUEST_ID_KEY)
+        Log.i(TAG, "Autofill sessionId: $sessionId, autofill requestId: $requestId")
+        if (sessionId == 0 || requestId == 0) {
+            Log.i(TAG, "Session Id or request Id not found")
+            callback.onFailure("Session Id or request Id not found")
             return
         }
 
-        val getCredRequest: GetCredentialRequest? = getCredManRequest(structure)
+        val getCredRequest: GetCredentialRequest? = getCredManRequest(structure, sessionId,
+            requestId)
         if (getCredRequest == null) {
             Log.i(TAG, "No credential manager request found")
             callback.onFailure("No credential manager request found")
@@ -515,12 +523,19 @@
         TODO("Not yet implemented")
     }
 
-    private fun getCredManRequest(structure: AssistStructure): GetCredentialRequest? {
+    private fun getCredManRequest(
+        structure: AssistStructure,
+        sessionId: Int,
+        requestId: Int
+    ): GetCredentialRequest? {
         val credentialOptions: MutableList<CredentialOption> = mutableListOf()
         traverseStructure(structure, credentialOptions)
 
         if (credentialOptions.isNotEmpty()) {
-            return GetCredentialRequest.Builder(Bundle.EMPTY)
+            val dataBundle = Bundle()
+            dataBundle.putInt(SESSION_ID_KEY, sessionId)
+            dataBundle.putInt(REQUEST_ID_KEY, requestId)
+            return GetCredentialRequest.Builder(dataBundle)
                     .setCredentialOptions(credentialOptions)
                     .build()
         }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
index 170cb45..9ad3e3c 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
@@ -91,7 +91,8 @@
         // be stale, if e.g. the app was uninstalled while the activity was destroyed.
         super.onCreate(null);
 
-        if (usePiaV2() && !isTv()) {
+        // TODO(b/318521110) Enable PIA v2 for archive dialog.
+        if (usePiaV2() && !isTv() && !isArchiveDialog(getIntent())) {
             Log.i(TAG, "Using Pia V2");
 
             boolean returnResult = getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false);
@@ -224,6 +225,11 @@
         showConfirmationDialog();
     }
 
+    private boolean isArchiveDialog(Intent intent) {
+        return (intent.getIntExtra(PackageInstaller.EXTRA_DELETE_FLAGS, 0)
+                & PackageManager.DELETE_ARCHIVE) != 0;
+    }
+
     /**
      * Parses specific {@link android.content.pm.PackageManager.DeleteFlags} from {@link Intent}
      * to archive an app if requested.
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
index 0e40db2..f44b161 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
@@ -14,11 +14,10 @@
   See the License for the specific language governing permissions and
   limitations under the License.
   -->
-<resources
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+<resources>
     <style name="TextAppearance.PreferenceTitle.SettingsLib"
            parent="@android:style/TextAppearance.Material.Subhead">
-        <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
+        <item name="android:textColor">@color/settingslib_text_color_primary</item>
         <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item>
         <item name="android:textSize">20sp</item>
     </style>
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index d201544..d3d8e4e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -172,7 +172,7 @@
         gridModifier =
             gridModifier
                 .fillMaxSize()
-                .dragContainer(dragDropState, beforeContentPadding(contentPadding))
+                .dragContainer(dragDropState, beforeContentPadding(contentPadding), viewModel)
                 .onGloballyPositioned { setGridCoordinates(it) }
         // for widgets dropped from other activities
         val dragAndDropTargetState =
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
index 1b40de4..1138221 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
@@ -40,6 +40,7 @@
 import androidx.compose.ui.unit.toSize
 import androidx.compose.ui.zIndex
 import com.android.systemui.communal.ui.compose.extensions.plus
+import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.launch
@@ -207,7 +208,8 @@
 
 fun Modifier.dragContainer(
     dragDropState: GridDragDropState,
-    beforeContentPadding: ContentPaddingInPx
+    beforeContentPadding: ContentPaddingInPx,
+    viewModel: BaseCommunalViewModel,
 ): Modifier {
     return pointerInput(dragDropState, beforeContentPadding) {
         detectDragGesturesAfterLongPress(
@@ -220,9 +222,16 @@
                     offset,
                     Offset(beforeContentPadding.startPadding, beforeContentPadding.topPadding)
                 )
+                viewModel.onReorderWidgetStart()
             },
-            onDragEnd = { dragDropState.onDragInterrupted() },
-            onDragCancel = { dragDropState.onDragInterrupted() }
+            onDragEnd = {
+                dragDropState.onDragInterrupted()
+                viewModel.onReorderWidgetEnd()
+            },
+            onDragCancel = {
+                dragDropState.onDragInterrupted()
+                viewModel.onReorderWidgetCancel()
+            }
         )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index 449ee6f..4079f12 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -19,14 +19,14 @@
 import android.appwidget.AppWidgetHost
 import android.appwidget.AppWidgetManager
 import android.appwidget.AppWidgetProviderInfo
-import android.content.BroadcastReceiver
 import android.content.ComponentName
+import android.content.Intent
+import android.content.Intent.ACTION_USER_UNLOCKED
 import android.os.UserHandle
 import android.os.UserManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.communal.data.db.CommunalItemRank
 import com.android.systemui.communal.data.db.CommunalWidgetDao
 import com.android.systemui.communal.data.db.CommunalWidgetItem
@@ -38,15 +38,12 @@
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.kotlinArgumentCaptor
-import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import java.util.Optional
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
@@ -55,8 +52,8 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito
 import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -70,8 +67,6 @@
 
     @Mock private lateinit var appWidgetHost: AppWidgetHost
 
-    @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
-
     @Mock private lateinit var userManager: UserManager
 
     @Mock private lateinit var userHandle: UserHandle
@@ -125,10 +120,10 @@
         testScope.runTest {
             communalEnabled(false)
             val repository = initCommunalWidgetRepository()
-            collectLastValue(repository.communalWidgets)()
+            repository.communalWidgets.launchIn(backgroundScope)
             runCurrent()
 
-            verify(communalWidgetDao, Mockito.never()).getWidgets()
+            verify(communalWidgetDao, never()).getWidgets()
         }
 
     @Test
@@ -136,10 +131,10 @@
         testScope.runTest {
             userUnlocked(false)
             val repository = initCommunalWidgetRepository()
-            collectLastValue(repository.communalWidgets)()
+            repository.communalWidgets.launchIn(backgroundScope)
             runCurrent()
 
-            verify(communalWidgetDao, Mockito.never()).getWidgets()
+            verify(communalWidgetDao, never()).getWidgets()
         }
 
     @Test
@@ -147,8 +142,7 @@
         testScope.runTest {
             userUnlocked(false)
             val repository = initCommunalWidgetRepository()
-            val communalWidgets = collectLastValue(repository.communalWidgets)
-            communalWidgets()
+            val communalWidgets by collectLastValue(repository.communalWidgets)
             runCurrent()
             val communalItemRankEntry = CommunalItemRank(uid = 1L, rank = 1)
             val communalWidgetItemEntry = CommunalWidgetItem(uid = 1L, 1, "pk_name/cls_name", 1L)
@@ -158,11 +152,14 @@
 
             userUnlocked(true)
             installedProviders(listOf(stopwatchProviderInfo))
-            broadcastReceiverUpdate()
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(ACTION_USER_UNLOCKED)
+            )
             runCurrent()
 
             verify(communalWidgetDao).getWidgets()
-            assertThat(communalWidgets())
+            assertThat(communalWidgets)
                 .containsExactly(
                     CommunalWidgetContentModel(
                         appWidgetId = communalWidgetItemEntry.widgetId,
@@ -182,9 +179,10 @@
             val provider = ComponentName("pkg_name", "cls_name")
             val id = 1
             val priority = 1
+            whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(true)
             whenever(communalWidgetHost.allocateIdAndBindWidget(any<ComponentName>()))
                 .thenReturn(id)
-            repository.addWidget(provider, priority)
+            repository.addWidget(provider, priority) { true }
             runCurrent()
 
             verify(communalWidgetHost).allocateIdAndBindWidget(provider)
@@ -192,6 +190,71 @@
         }
 
     @Test
+    fun addWidget_configurationFails_doNotAddWidgetToDb() =
+        testScope.runTest {
+            userUnlocked(true)
+            val repository = initCommunalWidgetRepository()
+            runCurrent()
+
+            val provider = ComponentName("pkg_name", "cls_name")
+            val id = 1
+            val priority = 1
+            whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(true)
+            whenever(communalWidgetHost.allocateIdAndBindWidget(provider)).thenReturn(id)
+            repository.addWidget(provider, priority) { false }
+            runCurrent()
+
+            verify(communalWidgetHost).allocateIdAndBindWidget(provider)
+            verify(communalWidgetDao, never()).addWidget(id, provider, priority)
+            verify(appWidgetHost).deleteAppWidgetId(id)
+        }
+
+    @Test
+    fun addWidget_configurationThrowsError_doNotAddWidgetToDb() =
+        testScope.runTest {
+            userUnlocked(true)
+            val repository = initCommunalWidgetRepository()
+            runCurrent()
+
+            val provider = ComponentName("pkg_name", "cls_name")
+            val id = 1
+            val priority = 1
+            whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(true)
+            whenever(communalWidgetHost.allocateIdAndBindWidget(provider)).thenReturn(id)
+            repository.addWidget(provider, priority) { throw IllegalStateException("some error") }
+            runCurrent()
+
+            verify(communalWidgetHost).allocateIdAndBindWidget(provider)
+            verify(communalWidgetDao, never()).addWidget(id, provider, priority)
+            verify(appWidgetHost).deleteAppWidgetId(id)
+        }
+
+    @Test
+    fun addWidget_configurationNotRequired_doesNotConfigure_addWidgetToDb() =
+        testScope.runTest {
+            userUnlocked(true)
+            val repository = initCommunalWidgetRepository()
+            runCurrent()
+
+            val provider = ComponentName("pkg_name", "cls_name")
+            val id = 1
+            val priority = 1
+            whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(false)
+            whenever(communalWidgetHost.allocateIdAndBindWidget(any<ComponentName>()))
+                .thenReturn(id)
+            var configured = false
+            repository.addWidget(provider, priority) {
+                configured = true
+                true
+            }
+            runCurrent()
+
+            verify(communalWidgetHost).allocateIdAndBindWidget(provider)
+            verify(communalWidgetDao).addWidget(id, provider, priority)
+            assertThat(configured).isFalse()
+        }
+
+    @Test
     fun deleteWidget_removeWidgetId_andDeleteFromDb() =
         testScope.runTest {
             userUnlocked(true)
@@ -225,17 +288,9 @@
         testScope.runTest {
             communalEnabled(false)
             val repository = initCommunalWidgetRepository()
-            collectLastValue(repository.communalWidgets)()
-            verifyBroadcastReceiverNeverRegistered()
-        }
-
-    @Test
-    fun broadcastReceiver_featureEnabledAndUserUnlocked_doNotRegisterBroadcastReceiver() =
-        testScope.runTest {
-            userUnlocked(true)
-            val repository = initCommunalWidgetRepository()
-            collectLastValue(repository.communalWidgets)()
-            verifyBroadcastReceiverNeverRegistered()
+            repository.communalWidgets.launchIn(backgroundScope)
+            runCurrent()
+            assertThat(fakeBroadcastDispatcher.numReceiversRegistered).isEqualTo(0)
         }
 
     @Test
@@ -243,24 +298,9 @@
         testScope.runTest {
             userUnlocked(false)
             val repository = initCommunalWidgetRepository()
-            collectLastValue(repository.communalWidgets)()
-            verifyBroadcastReceiverRegistered()
-        }
-
-    @Test
-    fun broadcastReceiver_whenFlowFinishes_unregisterBroadcastReceiver() =
-        testScope.runTest {
-            userUnlocked(false)
-            val repository = initCommunalWidgetRepository()
-
-            val job = launch { repository.communalWidgets.collect() }
+            repository.communalWidgets.launchIn(backgroundScope)
             runCurrent()
-            val receiver = broadcastReceiverUpdate()
-
-            job.cancel()
-            runCurrent()
-
-            verify(broadcastDispatcher).unregisterReceiver(receiver)
+            assertThat(fakeBroadcastDispatcher.numReceiversRegistered).isEqualTo(1)
         }
 
     @Test
@@ -268,12 +308,16 @@
         testScope.runTest {
             userUnlocked(false)
             val repository = initCommunalWidgetRepository()
-            collectLastValue(repository.communalWidgets)()
-            verify(appWidgetHost, Mockito.never()).startListening()
+            repository.communalWidgets.launchIn(backgroundScope)
+            runCurrent()
+            verify(appWidgetHost, never()).startListening()
 
             userUnlocked(true)
-            broadcastReceiverUpdate()
-            collectLastValue(repository.communalWidgets)()
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(ACTION_USER_UNLOCKED)
+            )
+            runCurrent()
 
             verify(appWidgetHost).startListening()
         }
@@ -283,18 +327,25 @@
         testScope.runTest {
             userUnlocked(false)
             val repository = initCommunalWidgetRepository()
-            collectLastValue(repository.communalWidgets)()
+            repository.communalWidgets.launchIn(backgroundScope)
+            runCurrent()
 
             userUnlocked(true)
-            broadcastReceiverUpdate()
-            collectLastValue(repository.communalWidgets)()
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(ACTION_USER_UNLOCKED)
+            )
+            runCurrent()
 
             verify(appWidgetHost).startListening()
-            verify(appWidgetHost, Mockito.never()).stopListening()
+            verify(appWidgetHost, never()).stopListening()
 
             userUnlocked(false)
-            broadcastReceiverUpdate()
-            collectLastValue(repository.communalWidgets)()
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(ACTION_USER_UNLOCKED)
+            )
+            runCurrent()
 
             verify(appWidgetHost).stopListening()
         }
@@ -305,7 +356,7 @@
             appWidgetHost,
             testScope.backgroundScope,
             testDispatcher,
-            broadcastDispatcher,
+            fakeBroadcastDispatcher,
             communalRepository,
             communalWidgetHost,
             communalWidgetDao,
@@ -315,45 +366,6 @@
         )
     }
 
-    private fun verifyBroadcastReceiverRegistered() {
-        verify(broadcastDispatcher)
-            .registerReceiver(
-                any(),
-                any(),
-                nullable(),
-                nullable(),
-                anyInt(),
-                nullable(),
-            )
-    }
-
-    private fun verifyBroadcastReceiverNeverRegistered() {
-        verify(broadcastDispatcher, Mockito.never())
-            .registerReceiver(
-                any(),
-                any(),
-                nullable(),
-                nullable(),
-                anyInt(),
-                nullable(),
-            )
-    }
-
-    private fun broadcastReceiverUpdate(): BroadcastReceiver {
-        val broadcastReceiverCaptor = kotlinArgumentCaptor<BroadcastReceiver>()
-        verify(broadcastDispatcher)
-            .registerReceiver(
-                broadcastReceiverCaptor.capture(),
-                any(),
-                nullable(),
-                nullable(),
-                anyInt(),
-                nullable(),
-            )
-        broadcastReceiverCaptor.value.onReceive(null, null)
-        return broadcastReceiverCaptor.value
-    }
-
     private fun communalEnabled(enabled: Boolean) {
         communalRepository.setIsCommunalEnabled(enabled)
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 4a935d0..ff6fd43 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -16,12 +16,17 @@
 
 package com.android.systemui.communal.view.viewmodel
 
+import android.app.Activity.RESULT_CANCELED
+import android.app.Activity.RESULT_OK
 import android.app.smartspace.SmartspaceTarget
+import android.appwidget.AppWidgetHost
+import android.content.ComponentName
 import android.os.PowerManager
 import android.provider.Settings
 import android.widget.RemoteViews
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
 import com.android.systemui.communal.data.repository.FakeCommunalRepository
@@ -29,6 +34,7 @@
 import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
 import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
 import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.shared.log.CommunalUiEvent
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
 import com.android.systemui.coroutines.collectLastValue
@@ -42,20 +48,26 @@
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import javax.inject.Provider
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class CommunalEditModeViewModelTest : SysuiTestCase() {
     @Mock private lateinit var mediaHost: MediaHost
     @Mock private lateinit var shadeViewController: ShadeViewController
     @Mock private lateinit var powerManager: PowerManager
+    @Mock private lateinit var appWidgetHost: AppWidgetHost
+    @Mock private lateinit var uiEventLogger: UiEventLogger
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
@@ -73,7 +85,7 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        val withDeps = CommunalInteractorFactory.create()
+        val withDeps = CommunalInteractorFactory.create(testScope)
         keyguardRepository = withDeps.keyguardRepository
         communalRepository = withDeps.communalRepository
         tutorialRepository = withDeps.tutorialRepository
@@ -84,9 +96,11 @@
         underTest =
             CommunalEditModeViewModel(
                 withDeps.communalInteractor,
+                appWidgetHost,
                 Provider { shadeViewController },
                 powerManager,
                 mediaHost,
+                uiEventLogger,
             )
     }
 
@@ -145,4 +159,71 @@
             )
             .isEqualTo(false)
     }
+
+    @Test
+    fun addingWidgetTriggersConfiguration() =
+        testScope.runTest {
+            val provider = ComponentName("pkg.test", "testWidget")
+            val widgetToConfigure by collectLastValue(underTest.widgetsToConfigure)
+            assertThat(widgetToConfigure).isNull()
+            underTest.onAddWidget(componentName = provider, priority = 0)
+            assertThat(widgetToConfigure).isEqualTo(1)
+        }
+
+    @Test
+    fun settingResultOkAddsWidget() =
+        testScope.runTest {
+            val provider = ComponentName("pkg.test", "testWidget")
+            val widgetAdded by collectLastValue(widgetRepository.widgetAdded)
+            assertThat(widgetAdded).isNull()
+            underTest.onAddWidget(componentName = provider, priority = 0)
+            assertThat(widgetAdded).isNull()
+            underTest.setConfigurationResult(RESULT_OK)
+            assertThat(widgetAdded).isEqualTo(1)
+        }
+
+    @Test
+    fun settingResultCancelledDoesNotAddWidget() =
+        testScope.runTest {
+            val provider = ComponentName("pkg.test", "testWidget")
+            val widgetAdded by collectLastValue(widgetRepository.widgetAdded)
+            assertThat(widgetAdded).isNull()
+            underTest.onAddWidget(componentName = provider, priority = 0)
+            assertThat(widgetAdded).isNull()
+            underTest.setConfigurationResult(RESULT_CANCELED)
+            assertThat(widgetAdded).isNull()
+        }
+
+    @Test(expected = IllegalStateException::class)
+    fun settingResultBeforeWidgetAddedThrowsException() {
+        underTest.setConfigurationResult(RESULT_OK)
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun addingWidgetWhileConfigurationActiveFails() =
+        testScope.runTest {
+            val providerOne = ComponentName("pkg.test", "testWidget")
+            underTest.onAddWidget(componentName = providerOne, priority = 0)
+            runCurrent()
+            val providerTwo = ComponentName("pkg.test", "testWidget2")
+            underTest.onAddWidget(componentName = providerTwo, priority = 0)
+        }
+
+    @Test
+    fun reorderWidget_uiEventLogging_start() {
+        underTest.onReorderWidgetStart()
+        verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_START)
+    }
+
+    @Test
+    fun reorderWidget_uiEventLogging_end() {
+        underTest.onReorderWidgetEnd()
+        verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_FINISH)
+    }
+
+    @Test
+    fun reorderWidget_uiEventLogging_cancel() {
+        underTest.onReorderWidgetCancel()
+        verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_CANCEL)
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 7c3dc97..5b88ebe6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -32,7 +32,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.statusbar.notification.data.repository.fakeNotificationsKeyguardViewStateRepository
+import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
 import com.android.systemui.statusbar.phone.dozeParameters
 import com.android.systemui.statusbar.phone.screenOffAnimationController
 import com.android.systemui.testKosmos
@@ -56,8 +56,7 @@
     private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
     private val screenOffAnimationController = kosmos.screenOffAnimationController
     private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
-    private val fakeNotificationsKeyguardViewStateRepository =
-        kosmos.fakeNotificationsKeyguardViewStateRepository
+    private val notificationsKeyguardInteractor = kosmos.notificationsKeyguardInteractor
     private val dozeParameters = kosmos.dozeParameters
     private val underTest = kosmos.keyguardRootViewModel
 
@@ -118,7 +117,7 @@
         testScope.runTest {
             val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
             runCurrent()
-            fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(true)
+            notificationsKeyguardInteractor.setPulseExpanding(true)
             deviceEntryRepository.setBypassEnabled(false)
             runCurrent()
 
@@ -130,9 +129,9 @@
         testScope.runTest {
             val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
             runCurrent()
-            fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false)
+            notificationsKeyguardInteractor.setPulseExpanding(false)
             deviceEntryRepository.setBypassEnabled(true)
-            fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true)
+            notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
             runCurrent()
 
             assertThat(isVisible?.value).isTrue()
@@ -144,10 +143,10 @@
         testScope.runTest {
             val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
             runCurrent()
-            fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false)
+            notificationsKeyguardInteractor.setPulseExpanding(false)
             deviceEntryRepository.setBypassEnabled(false)
             whenever(dozeParameters.alwaysOn).thenReturn(false)
-            fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true)
+            notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
             runCurrent()
 
             assertThat(isVisible?.value).isTrue()
@@ -159,11 +158,11 @@
         testScope.runTest {
             val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
             runCurrent()
-            fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false)
+            notificationsKeyguardInteractor.setPulseExpanding(false)
             deviceEntryRepository.setBypassEnabled(false)
             whenever(dozeParameters.alwaysOn).thenReturn(true)
             whenever(dozeParameters.displayNeedsBlanking).thenReturn(true)
-            fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true)
+            notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
             runCurrent()
 
             assertThat(isVisible?.value).isTrue()
@@ -175,11 +174,11 @@
         testScope.runTest {
             val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
             runCurrent()
-            fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false)
+            notificationsKeyguardInteractor.setPulseExpanding(false)
             deviceEntryRepository.setBypassEnabled(false)
             whenever(dozeParameters.alwaysOn).thenReturn(true)
             whenever(dozeParameters.displayNeedsBlanking).thenReturn(false)
-            fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true)
+            notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
             runCurrent()
 
             assertThat(isVisible?.value).isTrue()
@@ -191,11 +190,11 @@
         testScope.runTest {
             val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
             runCurrent()
-            fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false)
+            notificationsKeyguardInteractor.setPulseExpanding(false)
             deviceEntryRepository.setBypassEnabled(false)
             whenever(dozeParameters.alwaysOn).thenReturn(true)
             whenever(dozeParameters.displayNeedsBlanking).thenReturn(false)
-            fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true)
+            notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
             runCurrent()
 
             assertThat(isVisible?.isAnimating).isEqualTo(true)
diff --git a/packages/SystemUI/res/color/notification_state_color_dark.xml b/packages/SystemUI/res/color/notification_state_color_dark.xml
new file mode 100644
index 0000000..d26cbd5
--- /dev/null
+++ b/packages/SystemUI/res/color/notification_state_color_dark.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <!-- Pressed state's alpha is set to 0.00 temporarily until this bug is resolved permanently
+    b/313920497 Design intended alpha is 0.15-->
+    <item android:state_pressed="true" android:color="#ffffff" android:alpha="0.00" />
+    <item android:state_hovered="true" android:color="#ffffff" android:alpha="0.11" />
+    <item android:color="@color/transparent" />
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/notification_overlay_color.xml b/packages/SystemUI/res/color/notification_state_color_default.xml
similarity index 100%
rename from packages/SystemUI/res/color/notification_overlay_color.xml
rename to packages/SystemUI/res/color/notification_state_color_default.xml
diff --git a/packages/SystemUI/res/color/notification_state_color_light.xml b/packages/SystemUI/res/color/notification_state_color_light.xml
new file mode 100644
index 0000000..3e8bcf3
--- /dev/null
+++ b/packages/SystemUI/res/color/notification_state_color_light.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <!-- Pressed state's alpha is set to 0.00 temporarily until this bug is resolved permanently
+    b/313920497 Design intended alpha is 0.15-->
+    <item android:state_pressed="true" android:color="#000000" android:alpha="0.00" />
+    <item android:state_hovered="true" android:color="#000000" android:alpha="0.11" />
+    <item android:color="@color/transparent" />
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/notification_material_bg.xml b/packages/SystemUI/res/drawable/notification_material_bg.xml
index 355e75d..3f903ae 100644
--- a/packages/SystemUI/res/drawable/notification_material_bg.xml
+++ b/packages/SystemUI/res/drawable/notification_material_bg.xml
@@ -25,7 +25,7 @@
     </item>
     <item>
         <shape>
-            <solid android:color="@color/notification_overlay_color" />
+            <solid android:color="@color/notification_state_color_default" />
         </shape>
     </item>
 </layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 64a1d24..17719d1 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -1002,4 +1002,7 @@
         <item>com.android.switchaccess.SwitchAccessService</item>
         <item>com.google.android.apps.accessibility.voiceaccess.JustSpeakService</item>
     </string-array>
+
+    <!--  Whether to use a machine learning model for back gesture falsing. -->
+    <bool name="config_useBackGestureML">true</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 3f026a4..7d7c050 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -921,7 +921,7 @@
     <style name="Theme.ControlsActivity" parent="@android:style/Theme.DeviceDefault.NoActionBar">
         <item name="android:windowActivityTransitions">true</item>
         <item name="android:windowContentTransitions">false</item>
-        <item name="android:windowIsTranslucent">false</item>
+        <item name="android:windowIsTranslucent">true</item>
         <item name="android:windowBackground">@android:color/black</item>
         <item name="android:windowAnimationStyle">@null</item>
         <item name="android:statusBarColor">@android:color/black</item>
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index cab8adf..e6816e9 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -18,14 +18,12 @@
 
 import android.appwidget.AppWidgetHost
 import android.appwidget.AppWidgetManager
-import android.content.BroadcastReceiver
 import android.content.ComponentName
-import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
 import android.os.UserManager
+import androidx.annotation.WorkerThread
 import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.communal.data.db.CommunalItemRank
 import com.android.systemui.communal.data.db.CommunalWidgetDao
 import com.android.systemui.communal.data.db.CommunalWidgetItem
@@ -40,17 +38,21 @@
 import com.android.systemui.settings.UserTracker
 import java.util.Optional
 import javax.inject.Inject
+import kotlin.coroutines.cancellation.CancellationException
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 
 /** Encapsulates the state of widgets for communal mode. */
 interface CommunalWidgetRepository {
@@ -58,7 +60,11 @@
     val communalWidgets: Flow<List<CommunalWidgetContentModel>>
 
     /** Add a widget at the specified position in the app widget service and the database. */
-    fun addWidget(provider: ComponentName, priority: Int) {}
+    fun addWidget(
+        provider: ComponentName,
+        priority: Int,
+        configureWidget: suspend (id: Int) -> Boolean
+    ) {}
 
     /** Delete a widget by id from app widget service and the database. */
     fun deleteWidget(widgetId: Int) {}
@@ -97,37 +103,22 @@
     // Whether the [AppWidgetHost] is listening for updates.
     private var isHostListening = false
 
+    private suspend fun isUserUnlockingOrUnlocked(): Boolean =
+        withContext(bgDispatcher) { userManager.isUserUnlockingOrUnlocked(userTracker.userHandle) }
+
     private val isUserUnlocked: Flow<Boolean> =
-        callbackFlow {
-                if (!communalRepository.isCommunalEnabled) {
-                    awaitClose()
-                }
-
-                fun isUserUnlockingOrUnlocked(): Boolean {
-                    return userManager.isUserUnlockingOrUnlocked(userTracker.userHandle)
-                }
-
-                fun send() {
-                    trySendWithFailureLogging(isUserUnlockingOrUnlocked(), TAG)
-                }
-
-                if (isUserUnlockingOrUnlocked()) {
-                    send()
-                    awaitClose()
+        flowOf(communalRepository.isCommunalEnabled)
+            .flatMapLatest { enabled ->
+                if (enabled) {
+                    broadcastDispatcher
+                        .broadcastFlow(
+                            filter = IntentFilter(Intent.ACTION_USER_UNLOCKED),
+                            user = userTracker.userHandle
+                        )
+                        .onStart { emit(Unit) }
+                        .mapLatest { isUserUnlockingOrUnlocked() }
                 } else {
-                    val receiver =
-                        object : BroadcastReceiver() {
-                            override fun onReceive(context: Context?, intent: Intent?) {
-                                send()
-                            }
-                        }
-
-                    broadcastDispatcher.registerReceiver(
-                        receiver,
-                        IntentFilter(Intent.ACTION_USER_UNLOCKED),
-                    )
-
-                    awaitClose { broadcastDispatcher.unregisterReceiver(receiver) }
+                    emptyFlow()
                 }
             }
             .distinctUntilChanged()
@@ -148,18 +139,52 @@
             if (!isHostActive || !appWidgetManager.isPresent) {
                 return@flatMapLatest flowOf(emptyList())
             }
-            communalWidgetDao.getWidgets().map { it.map(::mapToContentModel) }
+            communalWidgetDao
+                .getWidgets()
+                .map { it.map(::mapToContentModel) }
+                // As this reads from a database and triggers IPCs to AppWidgetManager,
+                // it should be executed in the background.
+                .flowOn(bgDispatcher)
         }
 
-    override fun addWidget(provider: ComponentName, priority: Int) {
+    override fun addWidget(
+        provider: ComponentName,
+        priority: Int,
+        configureWidget: suspend (id: Int) -> Boolean
+    ) {
         applicationScope.launch(bgDispatcher) {
             val id = communalWidgetHost.allocateIdAndBindWidget(provider)
-            id?.let {
-                communalWidgetDao.addWidget(
-                    widgetId = it,
-                    provider = provider,
-                    priority = priority,
-                )
+            if (id != null) {
+                val configured =
+                    if (communalWidgetHost.requiresConfiguration(id)) {
+                        logger.i("Widget ${provider.flattenToString()} requires configuration.")
+                        try {
+                            configureWidget.invoke(id)
+                        } catch (ex: Exception) {
+                            // Cleanup the app widget id if an error happens during configuration.
+                            logger.e("Error during widget configuration, cleaning up id $id", ex)
+                            if (ex is CancellationException) {
+                                appWidgetHost.deleteAppWidgetId(id)
+                                // Re-throw cancellation to ensure the parent coroutine also gets
+                                // cancelled.
+                                throw ex
+                            } else {
+                                false
+                            }
+                        }
+                    } else {
+                        logger.i("Skipping configuration for ${provider.flattenToString()}")
+                        true
+                    }
+                if (configured) {
+                    communalWidgetDao.addWidget(
+                        widgetId = id,
+                        provider = provider,
+                        priority = priority,
+                    )
+                } else {
+                    appWidgetHost.deleteAppWidgetId(id)
+                }
             }
             logger.i("Added widget ${provider.flattenToString()} at position $priority.")
         }
@@ -182,6 +207,7 @@
         }
     }
 
+    @WorkerThread
     private fun mapToContentModel(
         entry: Map.Entry<CommunalItemRank, CommunalWidgetItem>
     ): CommunalWidgetContentModel {
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 8271238..24d4c6c 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
@@ -99,9 +99,17 @@
     /** Dismiss the CTA tile from the hub in view mode. */
     fun dismissCtaTile() = communalRepository.setCtaTileInViewModeVisibility(isVisible = false)
 
-    /** Add a widget at the specified position. */
-    fun addWidget(componentName: ComponentName, priority: Int) =
-        widgetRepository.addWidget(componentName, priority)
+    /**
+     * Add a widget at the specified position.
+     *
+     * @param configureWidget The callback to trigger if widget configuration is needed. Should
+     *   return whether configuration was successful.
+     */
+    fun addWidget(
+        componentName: ComponentName,
+        priority: Int,
+        configureWidget: suspend (id: Int) -> Boolean
+    ) = widgetRepository.addWidget(componentName, priority, configureWidget)
 
     /** Delete a widget by id. */
     fun deleteWidget(id: Int) = widgetRepository.deleteWidget(id)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt
index 155de32..41f9cb4 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt
@@ -18,6 +18,8 @@
 
 import android.appwidget.AppWidgetHost
 import android.appwidget.AppWidgetManager
+import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_CONFIGURATION_OPTIONAL
+import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE
 import android.content.ComponentName
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
@@ -63,4 +65,23 @@
         }
         return false
     }
+
+    /**
+     * Returns whether a particular widget requires configuration when it is first added.
+     *
+     * Must be called after the widget id has been bound.
+     */
+    fun requiresConfiguration(widgetId: Int): Boolean {
+        if (appWidgetManager.isPresent) {
+            val widgetInfo = appWidgetManager.get().getAppWidgetInfo(widgetId)
+            val featureFlags: Int = widgetInfo.widgetFeatures
+            // A widget's configuration is optional only if it's configuration is marked as optional
+            // AND it can be reconfigured later.
+            val configurationOptional =
+                (featureFlags and WIDGET_FEATURE_CONFIGURATION_OPTIONAL != 0 &&
+                    featureFlags and WIDGET_FEATURE_RECONFIGURABLE != 0)
+            return widgetInfo.configure != null && !configurationOptional
+        }
+        return false
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 4fabd97..4cb83a3 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -58,8 +58,16 @@
     /**
      * Called when a widget is added via drag and drop from the widget picker into the communal hub.
      */
-    fun onAddWidget(componentName: ComponentName, priority: Int) {
-        communalInteractor.addWidget(componentName, priority)
+    open fun onAddWidget(componentName: ComponentName, priority: Int) {
+        communalInteractor.addWidget(componentName, priority, ::configureWidget)
+    }
+
+    /**
+     * Called when a widget needs to be configured, with the id of the widget. The return value
+     * should represent whether configuring the widget was successful.
+     */
+    protected open suspend fun configureWidget(widgetId: Int): Boolean {
+        return true
     }
 
     // TODO(b/308813166): remove once CommunalContainer is moved lower in z-order and doesn't block
@@ -108,4 +116,13 @@
 
     /** Gets the interaction handler used to handle taps on a remote view */
     abstract fun getInteractionHandler(): RemoteViews.InteractionHandler
+
+    /** Called as the user starts dragging a widget to reorder. */
+    open fun onReorderWidgetStart() {}
+
+    /** Called as the user finishes dragging a widget to reorder. */
+    open fun onReorderWidgetEnd() {}
+
+    /** Called as the user cancels dragging a widget to reorder. */
+    open fun onReorderWidgetCancel() {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index d8e831c..0cbf3f13 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -16,18 +16,30 @@
 
 package com.android.systemui.communal.ui.viewmodel
 
+import android.app.Activity
+import android.app.Activity.RESULT_CANCELED
+import android.app.Activity.RESULT_OK
+import android.app.ActivityOptions
+import android.appwidget.AppWidgetHost
+import android.content.ActivityNotFoundException
+import android.content.ComponentName
 import android.os.PowerManager
 import android.widget.RemoteViews
+import com.android.internal.logging.UiEventLogger
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.shared.log.CommunalUiEvent
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.media.controls.ui.MediaHost
 import com.android.systemui.media.dagger.MediaModule
 import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.util.nullableAtomicReference
 import javax.inject.Inject
 import javax.inject.Named
 import javax.inject.Provider
+import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.map
 
 /** The view model for communal hub in edit mode. */
@@ -36,11 +48,28 @@
 @Inject
 constructor(
     private val communalInteractor: CommunalInteractor,
+    private val appWidgetHost: AppWidgetHost,
     shadeViewController: Provider<ShadeViewController>,
     powerManager: PowerManager,
     @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
+    private val uiEventLogger: UiEventLogger,
 ) : BaseCommunalViewModel(communalInteractor, shadeViewController, powerManager, mediaHost) {
 
+    private companion object {
+        private const val KEY_SPLASH_SCREEN_STYLE = "android.activity.splashScreenStyle"
+        private const val SPLASH_SCREEN_STYLE_EMPTY = 0
+    }
+
+    private val _widgetsToConfigure = MutableSharedFlow<Int>()
+
+    /**
+     * Flow emitting ids of widgets which need to be configured. The consumer of this flow should
+     * trigger [startConfigurationActivity] to initiate configuration.
+     */
+    val widgetsToConfigure: Flow<Int> = _widgetsToConfigure
+
+    private var pendingConfiguration: CompletableDeferred<Int>? by nullableAtomicReference()
+
     override val isEditMode = true
 
     // Only widgets are editable. The CTA tile comes last in the list and remains visible.
@@ -58,4 +87,67 @@
         // Ignore all interactions in edit mode.
         return RemoteViews.InteractionHandler { _, _, _ -> false }
     }
+
+    override fun onAddWidget(componentName: ComponentName, priority: Int) {
+        if (pendingConfiguration != null) {
+            throw IllegalStateException(
+                "Cannot add $componentName widget while widget configuration is pending"
+            )
+        }
+        super.onAddWidget(componentName, priority)
+    }
+
+    fun startConfigurationActivity(activity: Activity, widgetId: Int, requestCode: Int) {
+        val options =
+            ActivityOptions.makeBasic().apply {
+                setPendingIntentBackgroundActivityStartMode(
+                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+                )
+            }
+        val bundle = options.toBundle()
+        bundle.putInt(KEY_SPLASH_SCREEN_STYLE, SPLASH_SCREEN_STYLE_EMPTY)
+        try {
+            appWidgetHost.startAppWidgetConfigureActivityForResult(
+                activity,
+                widgetId,
+                0,
+                // Use the widget id as the request code.
+                requestCode,
+                bundle
+            )
+        } catch (e: ActivityNotFoundException) {
+            setConfigurationResult(RESULT_CANCELED)
+        }
+    }
+
+    override suspend fun configureWidget(widgetId: Int): Boolean {
+        if (pendingConfiguration != null) {
+            throw IllegalStateException(
+                "Attempting to configure $widgetId while another configuration is already active"
+            )
+        }
+        pendingConfiguration = CompletableDeferred()
+        _widgetsToConfigure.emit(widgetId)
+        val resultCode = pendingConfiguration?.await() ?: RESULT_CANCELED
+        pendingConfiguration = null
+        return resultCode == RESULT_OK
+    }
+
+    /** Sets the result of widget configuration. */
+    fun setConfigurationResult(resultCode: Int) {
+        pendingConfiguration?.complete(resultCode)
+            ?: throw IllegalStateException("No widget pending configuration")
+    }
+
+    override fun onReorderWidgetStart() {
+        uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_START)
+    }
+
+    override fun onReorderWidgetEnd() {
+        uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_FINISH)
+    }
+
+    override fun onReorderWidgetCancel() {
+        uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_CANCEL)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index 0f94a92..bfc6f2b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -27,23 +27,26 @@
 import androidx.activity.ComponentActivity
 import androidx.activity.result.ActivityResultLauncher
 import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
 import com.android.systemui.compose.ComposeFacade.setCommunalEditWidgetActivityContent
 import javax.inject.Inject
+import kotlinx.coroutines.launch
 
 /** An Activity for editing the widgets that appear in hub mode. */
 class EditWidgetsActivity
 @Inject
 constructor(
     private val communalViewModel: CommunalEditModeViewModel,
-    private val communalInteractor: CommunalInteractor,
     private var windowManagerService: IWindowManager? = null,
 ) : ComponentActivity() {
     companion object {
         private const val EXTRA_IS_PENDING_WIDGET_DRAG = "is_pending_widget_drag"
         private const val EXTRA_FILTER_STRATEGY = "filter_strategy"
         private const val FILTER_STRATEGY_GLANCEABLE_HUB = 1
+        private const val REQUEST_CODE_CONFIGURE_WIDGET = 1
         private const val TAG = "EditWidgetsActivity"
     }
 
@@ -63,7 +66,7 @@
                                     Intent.EXTRA_COMPONENT_NAME,
                                     ComponentName::class.java
                                 )
-                                ?.let { communalInteractor.addWidget(it, 0) }
+                                ?.let { communalViewModel.onAddWidget(it, 0) }
                                 ?: run { Log.w(TAG, "No AppWidgetProviderInfo found in result.") }
                         }
                     }
@@ -84,14 +87,26 @@
         windowInsetsController?.hide(WindowInsets.Type.systemBars())
         window.setDecorFitsSystemWindows(false)
 
+        lifecycleScope.launch {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                // Start the configuration activity when new widgets are added.
+                communalViewModel.widgetsToConfigure.collect { widgetId ->
+                    communalViewModel.startConfigurationActivity(
+                        activity = this@EditWidgetsActivity,
+                        widgetId = widgetId,
+                        requestCode = REQUEST_CODE_CONFIGURE_WIDGET
+                    )
+                }
+            }
+        }
+
         setCommunalEditWidgetActivityContent(
             activity = this,
             viewModel = communalViewModel,
             onOpenWidgetPicker = {
-                val localPackageManager: PackageManager = getPackageManager()
                 val intent =
                     Intent(Intent.ACTION_MAIN).also { it.addCategory(Intent.CATEGORY_HOME) }
-                localPackageManager
+                packageManager
                     .resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
                     ?.activityInfo
                     ?.packageName
@@ -122,4 +137,11 @@
             }
         )
     }
+
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        super.onActivityResult(requestCode, resultCode, data)
+        if (requestCode == REQUEST_CODE_CONFIGURE_WIDGET) {
+            communalViewModel.setConfigurationResult(resultCode)
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index e660b97..0d641ac 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -758,7 +758,8 @@
     }
 
     private void updateMLModelState() {
-        boolean newState = mIsGestureHandlingEnabled && DeviceConfig.getBoolean(
+        boolean newState = mIsGestureHandlingEnabled && mContext.getResources().getBoolean(
+                R.bool.config_useBackGestureML) && DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_SYSTEMUI,
                 SystemUiDeviceConfigFlags.USE_BACK_GESTURE_ML_MODEL, false);
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index a5c1cf1..6f4a1e7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -4362,8 +4362,7 @@
         @Override
         public void onHeadsUpPinned(NotificationEntry entry) {
             if (!isKeyguardShowing()) {
-                mNotificationStackScrollLayoutController.generateHeadsUpAnimation(
-                        entry.getHeadsUpAnimationView(), true);
+                mNotificationStackScrollLayoutController.generateHeadsUpAnimation(entry, true);
             }
         }
 
@@ -4375,8 +4374,7 @@
             // notification
             // will stick to the top without any interaction.
             if (isFullyCollapsed() && entry.isRowHeadsUp() && !isKeyguardShowing()) {
-                mNotificationStackScrollLayoutController.generateHeadsUpAnimation(
-                        entry.getHeadsUpAnimationView(), false);
+                mNotificationStackScrollLayoutController.generateHeadsUpAnimation(entry, false);
                 entry.setHeadsUpIsVisible();
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 0c67279..3f2c818 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
 import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
@@ -58,6 +59,7 @@
     private val dozeParameters: DozeParameters,
     private val screenOffAnimationController: ScreenOffAnimationController,
     private val logger: NotificationWakeUpCoordinatorLogger,
+    private val notifsKeyguardInteractor: NotificationsKeyguardInteractor,
 ) :
     OnHeadsUpChangedListener,
     StatusBarStateController.StateListener,
@@ -144,6 +146,7 @@
                 for (listener in wakeUpListeners) {
                     listener.onFullyHiddenChanged(value)
                 }
+                notifsKeyguardInteractor.setNotificationsFullyHidden(value)
             }
         }
 
@@ -216,6 +219,7 @@
                 for (listener in wakeUpListeners) {
                     listener.onPulseExpandingChanged(pulseExpanding)
                 }
+                notifsKeyguardInteractor.setPulseExpanding(pulseExpanding)
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
index 5435fb5..2cac000 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
@@ -15,8 +15,6 @@
  */
 package com.android.systemui.statusbar.notification.data
 
-import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardStateRepositoryModule
 import dagger.Module
 
-@Module(includes = [NotificationsKeyguardStateRepositoryModule::class])
-interface NotificationDataLayerModule
+@Module(includes = []) interface NotificationDataLayerModule
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt
index 2cc1403..bd6ea30 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt
@@ -15,59 +15,16 @@
  */
 package com.android.systemui.statusbar.notification.data.repository
 
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
-import dagger.Binds
-import dagger.Module
 import javax.inject.Inject
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
 
 /** View-states pertaining to notifications on the keyguard. */
-interface NotificationsKeyguardViewStateRepository {
+@SysUISingleton
+class NotificationsKeyguardViewStateRepository @Inject constructor() {
     /** Are notifications fully hidden from view? */
-    val areNotificationsFullyHidden: Flow<Boolean>
+    val areNotificationsFullyHidden = MutableStateFlow(false)
 
     /** Is a pulse expansion occurring? */
-    val isPulseExpanding: Flow<Boolean>
-}
-
-@Module
-interface NotificationsKeyguardStateRepositoryModule {
-    @Binds
-    fun bindImpl(
-        impl: NotificationsKeyguardViewStateRepositoryImpl
-    ): NotificationsKeyguardViewStateRepository
-}
-
-@SysUISingleton
-class NotificationsKeyguardViewStateRepositoryImpl
-@Inject
-constructor(
-    wakeUpCoordinator: NotificationWakeUpCoordinator,
-) : NotificationsKeyguardViewStateRepository {
-    override val areNotificationsFullyHidden: Flow<Boolean> = conflatedCallbackFlow {
-        val listener =
-            object : NotificationWakeUpCoordinator.WakeUpListener {
-                override fun onFullyHiddenChanged(isFullyHidden: Boolean) {
-                    trySend(isFullyHidden)
-                }
-            }
-        trySend(wakeUpCoordinator.notificationsFullyHidden)
-        wakeUpCoordinator.addListener(listener)
-        awaitClose { wakeUpCoordinator.removeListener(listener) }
-    }
-
-    override val isPulseExpanding: Flow<Boolean> = conflatedCallbackFlow {
-        val listener =
-            object : NotificationWakeUpCoordinator.WakeUpListener {
-                override fun onPulseExpandingChanged(isPulseExpanding: Boolean) {
-                    trySend(isPulseExpanding)
-                }
-            }
-        trySend(wakeUpCoordinator.isPulseExpanding())
-        wakeUpCoordinator.addListener(listener)
-        awaitClose { wakeUpCoordinator.removeListener(listener) }
-    }
+    val isPulseExpanding = MutableStateFlow(false)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt
index 73341db..a6361cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt
@@ -15,24 +15,29 @@
  */
 package com.android.systemui.statusbar.notification.domain.interactor
 
-import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardViewStateRepository
 import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flowOn
 
 /** Domain logic pertaining to notifications on the keyguard. */
 class NotificationsKeyguardInteractor
 @Inject
 constructor(
-    repository: NotificationsKeyguardViewStateRepository,
-    @Background backgroundDispatcher: CoroutineDispatcher,
+    private val repository: NotificationsKeyguardViewStateRepository,
 ) {
     /** Is a pulse expansion occurring? */
-    val isPulseExpanding: Flow<Boolean> = repository.isPulseExpanding.flowOn(backgroundDispatcher)
+    val isPulseExpanding: Flow<Boolean> = repository.isPulseExpanding
 
     /** Are notifications fully hidden from view? */
-    val areNotificationsFullyHidden: Flow<Boolean> =
-        repository.areNotificationsFullyHidden.flowOn(backgroundDispatcher)
+    val areNotificationsFullyHidden: Flow<Boolean> = repository.areNotificationsFullyHidden
+
+    /** Updates whether notifications are fully hidden from view. */
+    fun setNotificationsFullyHidden(fullyHidden: Boolean) {
+        repository.areNotificationsFullyHidden.value = fullyHidden
+    }
+
+    /** Updates whether a pulse expansion is occurring. */
+    fun setPulseExpanding(pulseExpanding: Boolean) {
+        repository.isPulseExpanding.value = pulseExpanding
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
index f6431a2..7ea9b14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
@@ -32,6 +32,8 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.internal.util.ContrastColorUtil;
+import com.android.settingslib.Utils;
 import com.android.systemui.Dumpable;
 import com.android.systemui.res.R;
 
@@ -58,11 +60,19 @@
     private int mExpandAnimationWidth = -1;
     private int mExpandAnimationHeight = -1;
     private int mDrawableAlpha = 255;
+    private final ColorStateList mLightColoredStatefulColors;
+    private final ColorStateList mDarkColoredStatefulColors;
+    private final int mNormalColor;
 
     public NotificationBackgroundView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mDontModifyCorners = getResources().getBoolean(
-                R.bool.config_clipNotificationsToOutline);
+        mDontModifyCorners = getResources().getBoolean(R.bool.config_clipNotificationsToOutline);
+        mLightColoredStatefulColors = getResources().getColorStateList(
+                R.color.notification_state_color_light);
+        mDarkColoredStatefulColors = getResources().getColorStateList(
+                R.color.notification_state_color_dark);
+        mNormalColor = Utils.getColorAttrDefaultColor(mContext,
+                com.android.internal.R.attr.materialColorSurfaceContainerHigh);
     }
 
     @Override
@@ -122,6 +132,18 @@
     }
 
     /**
+     * Stateful colors are colors that will overlay on the notification original color when one of
+     * hover states, pressed states or other similar states is activated.
+     */
+    private void setStatefulColors() {
+        if (mTintColor != mNormalColor) {
+            ColorStateList newColor = ContrastColorUtil.isColorDark(mTintColor)
+                    ? mDarkColoredStatefulColors : mLightColoredStatefulColors;
+            ((GradientDrawable) getStatefulBackgroundLayer().mutate()).setColor(newColor);
+        }
+    }
+
+    /**
      * Sets a background drawable. As we need to change our bounds independently of layout, we need
      * the notion of a background independently of the regular View background..
      */
@@ -149,21 +171,20 @@
         setCustomBackground(d);
     }
 
-    public void setTint(int tintColor) {
-        if (tintColor != 0) {
-            ColorStateList stateList = new ColorStateList(new int[][]{
-                    new int[]{com.android.internal.R.attr.state_pressed},
-                    new int[]{com.android.internal.R.attr.state_hovered},
-                    new int[]{}},
+    private Drawable getBaseBackgroundLayer() {
+        return ((LayerDrawable) mBackground).getDrawable(0);
+    }
 
-                    new int[]{tintColor, 0, tintColor}
-            );
-            mBackground.setTintMode(PorterDuff.Mode.SRC_ATOP);
-            mBackground.setTintList(stateList);
-        } else {
-            mBackground.setTintList(null);
-        }
+    private Drawable getStatefulBackgroundLayer() {
+        return ((LayerDrawable) mBackground).getDrawable(1);
+    }
+
+    public void setTint(int tintColor) {
+        Drawable baseLayer = getBaseBackgroundLayer();
+        baseLayer.mutate().setTintMode(PorterDuff.Mode.SRC_ATOP);
+        baseLayer.setTint(tintColor);
         mTintColor = tintColor;
+        setStatefulColors();
         invalidate();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 805b44c..ea414d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -4869,10 +4869,6 @@
 
     public void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) {
         ExpandableNotificationRow row = entry.getHeadsUpAnimationView();
-        generateHeadsUpAnimation(row, isHeadsUp);
-    }
-
-    public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
         final boolean add = mAnimationsEnabled && (isHeadsUp || mHeadsUpGoingAwayAnimationsAllowed);
         if (SPEW) {
             Log.v(TAG, "generateHeadsUpAnimation:"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 2e54512..abc04b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1495,14 +1495,10 @@
         return mView.getFirstChildNotGone();
     }
 
-    private void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) {
+    public void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) {
         mView.generateHeadsUpAnimation(entry, isHeadsUp);
     }
 
-    public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
-        mView.generateHeadsUpAnimation(row, isHeadsUp);
-    }
-
     public void setMaxTopPadding(int padding) {
         mView.setMaxTopPadding(padding);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/util/ReferenceExt.kt b/packages/SystemUI/src/com/android/systemui/util/ReferenceExt.kt
index ac04d31..4f7dce3 100644
--- a/packages/SystemUI/src/com/android/systemui/util/ReferenceExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/ReferenceExt.kt
@@ -2,6 +2,7 @@
 
 import java.lang.ref.SoftReference
 import java.lang.ref.WeakReference
+import java.util.concurrent.atomic.AtomicReference
 import kotlin.properties.ReadWriteProperty
 import kotlin.reflect.KProperty
 
@@ -48,3 +49,25 @@
         }
     }
 }
+
+/**
+ * Creates a nullable Kotlin idiomatic [AtomicReference].
+ *
+ * Usage:
+ * ```
+ * var atomicReferenceObj: Object? by nullableAtomicReference(null)
+ * atomicReferenceObj = Object()
+ * ```
+ */
+fun <T> nullableAtomicReference(obj: T? = null): ReadWriteProperty<Any?, T?> {
+    return object : ReadWriteProperty<Any?, T?> {
+        val t = AtomicReference(obj)
+        override fun getValue(thisRef: Any?, property: KProperty<*>): T? {
+            return t.get()
+        }
+
+        override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
+            t.set(value)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
index df5162a..3d724e1 100644
--- a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
+++ b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
@@ -22,12 +22,17 @@
 import android.content.Intent;
 import android.content.ServiceConnection;
 import android.os.IBinder;
+import android.util.IndentingPrintWriter;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.DumpUtilsKt;
 import com.android.systemui.util.annotations.WeaklyReferencedCallback;
 
+import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
@@ -244,6 +249,21 @@
         });
     }
 
+    void dump(@NonNull PrintWriter pw) {
+        IndentingPrintWriter ipw = DumpUtilsKt.asIndenting(pw);
+        ipw.println("ObservableServiceConnection state:");
+        DumpUtilsKt.withIncreasedIndent(ipw, () -> {
+            ipw.println("mServiceIntent: " + mServiceIntent);
+            ipw.println("mLastDisconnectReason: " + mLastDisconnectReason.orElse(-1));
+            ipw.println("Callbacks:");
+            DumpUtilsKt.withIncreasedIndent(ipw, () -> {
+                for (WeakReference<Callback<T>> cbRef : mCallbacks) {
+                    ipw.println(cbRef.get());
+                }
+            });
+        });
+    }
+
     private void applyToCallbacksLocked(Consumer<Callback<T>> applicator) {
         final Iterator<WeakReference<Callback<T>>> iterator = mCallbacks.iterator();
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
index 6e19bed..9b72eb7 100644
--- a/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
@@ -17,6 +17,7 @@
 package com.android.systemui.util.service;
 
 import static com.android.systemui.util.service.dagger.ObservableServiceModule.BASE_RECONNECT_DELAY_MS;
+import static com.android.systemui.util.service.dagger.ObservableServiceModule.DUMPSYS_NAME;
 import static com.android.systemui.util.service.dagger.ObservableServiceModule.MAX_RECONNECT_ATTEMPTS;
 import static com.android.systemui.util.service.dagger.ObservableServiceModule.MIN_CONNECTION_DURATION_MS;
 import static com.android.systemui.util.service.dagger.ObservableServiceModule.OBSERVER;
@@ -24,9 +25,15 @@
 
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
+import com.android.systemui.Dumpable;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.time.SystemClock;
 
+import java.io.PrintWriter;
+
 import javax.inject.Inject;
 import javax.inject.Named;
 
@@ -35,7 +42,7 @@
  * {@link ObservableServiceConnection}.
  * @param <T> The transformed connection type handled by the service.
  */
-public class PersistentConnectionManager<T> {
+public class PersistentConnectionManager<T> implements Dumpable {
     private static final String TAG = "PersistentConnManager";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
@@ -45,6 +52,8 @@
     private final int mMaxReconnectAttempts;
     private final int mMinConnectionDuration;
     private final Observer mObserver;
+    private final DumpManager mDumpManager;
+    private final String mDumpsysName;
 
     private int mReconnectAttempts = 0;
     private Runnable mCurrentReconnectCancelable;
@@ -89,6 +98,8 @@
     public PersistentConnectionManager(
             SystemClock clock,
             DelayableExecutor mainExecutor,
+            DumpManager dumpManager,
+            @Named(DUMPSYS_NAME) String dumpsysName,
             @Named(SERVICE_CONNECTION) ObservableServiceConnection<T> serviceConnection,
             @Named(MAX_RECONNECT_ATTEMPTS) int maxReconnectAttempts,
             @Named(BASE_RECONNECT_DELAY_MS) int baseReconnectDelayMs,
@@ -98,6 +109,8 @@
         mMainExecutor = mainExecutor;
         mConnection = serviceConnection;
         mObserver = observer;
+        mDumpManager = dumpManager;
+        mDumpsysName = TAG + "#" + dumpsysName;
 
         mMaxReconnectAttempts = maxReconnectAttempts;
         mBaseReconnectDelayMs = baseReconnectDelayMs;
@@ -108,6 +121,7 @@
      * Begins the {@link PersistentConnectionManager} by connecting to the associated service.
      */
     public void start() {
+        mDumpManager.registerCriticalDumpable(mDumpsysName, this);
         mConnection.addCallback(mConnectionCallback);
         mObserver.addCallback(mObserverCallback);
         initiateConnectionAttempt();
@@ -120,6 +134,32 @@
         mConnection.removeCallback(mConnectionCallback);
         mObserver.removeCallback(mObserverCallback);
         mConnection.unbind();
+        mDumpManager.unregisterDumpable(mDumpsysName);
+    }
+
+    /**
+     * Add a callback to the {@link ObservableServiceConnection}.
+     * @param callback The callback to add.
+     */
+    public void addConnectionCallback(ObservableServiceConnection.Callback<T> callback) {
+        mConnection.addCallback(callback);
+    }
+
+    /**
+     * Remove a callback from the {@link ObservableServiceConnection}.
+     * @param callback The callback to remove.
+     */
+    public void removeConnectionCallback(ObservableServiceConnection.Callback<T> callback) {
+        mConnection.removeCallback(callback);
+    }
+
+    @Override
+    public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+        pw.println("mMaxReconnectAttempts: " + mMaxReconnectAttempts);
+        pw.println("mBaseReconnectDelayMs: " + mBaseReconnectDelayMs);
+        pw.println("mMinConnectionDuration: " + mMinConnectionDuration);
+        pw.println("mReconnectAttempts: " + mReconnectAttempts);
+        mConnection.dump(pw);
     }
 
     private void initiateConnectionAttempt() {
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/dagger/ObservableServiceModule.java b/packages/SystemUI/src/com/android/systemui/util/service/dagger/ObservableServiceModule.java
index bcf34f8..c52c524 100644
--- a/packages/SystemUI/src/com/android/systemui/util/service/dagger/ObservableServiceModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/service/dagger/ObservableServiceModule.java
@@ -19,14 +19,14 @@
 
 import android.content.res.Resources;
 
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.qualifiers.Main;
-
-import javax.inject.Named;
+import com.android.systemui.res.R;
 
 import dagger.Module;
 import dagger.Provides;
 
+import javax.inject.Named;
+
 /**
  * Module containing components and parameters for
  * {@link com.android.systemui.util.service.ObservableServiceConnection}
@@ -41,6 +41,7 @@
     public static final String MIN_CONNECTION_DURATION_MS = "min_connection_duration_ms";
     public static final String SERVICE_CONNECTION = "service_connection";
     public static final String OBSERVER = "observer";
+    public static final String DUMPSYS_NAME = "dumpsys_name";
 
     @Provides
     @Named(MAX_RECONNECT_ATTEMPTS)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 3132767..a206581 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -152,7 +152,9 @@
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinatorLogger;
+import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardViewStateRepository;
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -586,6 +588,10 @@
         when(mPrimaryBouncerToGoneTransitionViewModel.getLockscreenAlpha())
                 .thenReturn(emptyFlow());
 
+        NotificationsKeyguardViewStateRepository notifsKeyguardViewStateRepository =
+                new NotificationsKeyguardViewStateRepository();
+        NotificationsKeyguardInteractor notifsKeyguardInteractor =
+                new NotificationsKeyguardInteractor(notifsKeyguardViewStateRepository);
         NotificationWakeUpCoordinator coordinator =
                 new NotificationWakeUpCoordinator(
                         mDumpManager,
@@ -596,7 +602,8 @@
                         mKeyguardBypassController,
                         mDozeParameters,
                         mScreenOffAnimationController,
-                        new NotificationWakeUpCoordinatorLogger(logcatLogBuffer()));
+                        new NotificationWakeUpCoordinatorLogger(logcatLogBuffer()),
+                        notifsKeyguardInteractor);
         mConfigurationController = new ConfigurationControllerImpl(mContext);
         PulseExpansionHandler expansionHandler = new PulseExpansionHandler(
                 mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
index 438b33d..039fef9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
@@ -22,12 +22,14 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.AnimatorTestRule
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.ShadeViewController.Companion.WAKEUP_ANIMATION_DELAY_MS
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_WAKEUP
+import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
 import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController
@@ -54,6 +56,8 @@
 
     @get:Rule val animatorTestRule = AnimatorTestRule()
 
+    private val kosmos = Kosmos()
+
     private val dumpManager: DumpManager = mock()
     private val headsUpManager: HeadsUpManager = mock()
     private val statusBarStateController: StatusBarStateController = mock()
@@ -100,6 +104,7 @@
                 dozeParameters,
                 screenOffAnimationController,
                 logger,
+                kosmos.notificationsKeyguardInteractor,
             )
         statusBarStateCallback = withArgCaptor {
             verify(statusBarStateController).addCallback(capture())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt
deleted file mode 100644
index 170f651..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.data.repository
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.collectLastValue
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
-import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.mockito.withArgCaptor
-import com.google.common.truth.Truth.assertThat
-import dagger.BindsInstance
-import dagger.Component
-import org.junit.Test
-import org.mockito.Mockito.verify
-
-@SmallTest
-class NotificationsKeyguardViewStateRepositoryTest : SysuiTestCase() {
-
-    @SysUISingleton
-    @Component(modules = [SysUITestModule::class])
-    interface TestComponent : SysUITestComponent<NotificationsKeyguardViewStateRepositoryImpl> {
-
-        val mockWakeUpCoordinator: NotificationWakeUpCoordinator
-
-        @Component.Factory
-        interface Factory {
-            fun create(
-                @BindsInstance test: SysuiTestCase,
-            ): TestComponent
-        }
-    }
-
-    private val testComponent: TestComponent =
-        DaggerNotificationsKeyguardViewStateRepositoryTest_TestComponent.factory()
-            .create(test = this)
-
-    @Test
-    fun areNotifsFullyHidden_reflectsWakeUpCoordinator() =
-        testComponent.runTest {
-            whenever(mockWakeUpCoordinator.notificationsFullyHidden).thenReturn(false)
-            val notifsFullyHidden by collectLastValue(underTest.areNotificationsFullyHidden)
-            runCurrent()
-
-            assertThat(notifsFullyHidden).isFalse()
-
-            withArgCaptor { verify(mockWakeUpCoordinator).addListener(capture()) }
-                .onFullyHiddenChanged(true)
-            runCurrent()
-
-            assertThat(notifsFullyHidden).isTrue()
-        }
-
-    @Test
-    fun isPulseExpanding_reflectsWakeUpCoordinator() =
-        testComponent.runTest {
-            whenever(mockWakeUpCoordinator.isPulseExpanding()).thenReturn(false)
-            val isPulseExpanding by collectLastValue(underTest.isPulseExpanding)
-            runCurrent()
-
-            assertThat(isPulseExpanding).isFalse()
-
-            withArgCaptor { verify(mockWakeUpCoordinator).addListener(capture()) }
-                .onPulseExpandingChanged(true)
-            runCurrent()
-
-            assertThat(isPulseExpanding).isTrue()
-        }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt
index bb3113a..3593f5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt
@@ -21,7 +21,6 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.runCurrent
 import com.android.systemui.runTest
-import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardViewStateRepository
 import com.google.common.truth.Truth.assertThat
 import dagger.BindsInstance
 import dagger.Component
@@ -33,9 +32,6 @@
     @SysUISingleton
     @Component(modules = [SysUITestModule::class])
     interface TestComponent : SysUITestComponent<NotificationsKeyguardInteractor> {
-
-        val repository: FakeNotificationsKeyguardViewStateRepository
-
         @Component.Factory
         interface Factory {
             fun create(@BindsInstance test: SysuiTestCase): TestComponent
@@ -48,13 +44,13 @@
     @Test
     fun areNotifsFullyHidden_reflectsRepository() =
         testComponent.runTest {
-            repository.setNotificationsFullyHidden(false)
+            underTest.setNotificationsFullyHidden(false)
             val notifsFullyHidden by collectLastValue(underTest.areNotificationsFullyHidden)
             runCurrent()
 
             assertThat(notifsFullyHidden).isFalse()
 
-            repository.setNotificationsFullyHidden(true)
+            underTest.setNotificationsFullyHidden(true)
             runCurrent()
 
             assertThat(notifsFullyHidden).isTrue()
@@ -63,13 +59,13 @@
     @Test
     fun isPulseExpanding_reflectsRepository() =
         testComponent.runTest {
-            repository.setPulseExpanding(false)
+            underTest.setPulseExpanding(false)
             val isPulseExpanding by collectLastValue(underTest.isPulseExpanding)
             runCurrent()
 
             assertThat(isPulseExpanding).isFalse()
 
-            repository.setPulseExpanding(true)
+            underTest.setPulseExpanding(true)
             runCurrent()
 
             assertThat(isPulseExpanding).isTrue()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
index 47feccf..7faf562 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
@@ -29,8 +29,8 @@
 import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
-import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardViewStateRepository
 import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
 import com.android.systemui.statusbar.notification.shared.byIsAmbient
 import com.android.systemui.statusbar.notification.shared.byIsLastMessageFromReply
 import com.android.systemui.statusbar.notification.shared.byIsPulsing
@@ -61,7 +61,7 @@
     interface TestComponent : SysUITestComponent<NotificationIconsInteractor> {
 
         val activeNotificationListRepository: ActiveNotificationListRepository
-        val keyguardViewStateRepository: FakeNotificationsKeyguardViewStateRepository
+        val notificationsKeyguardInteractor: NotificationsKeyguardInteractor
 
         @Component.Factory
         interface Factory {
@@ -136,7 +136,7 @@
     fun filteredEntrySet_noPulsing_notifsNotFullyHidden() =
         testComponent.runTest {
             val filteredSet by collectLastValue(underTest.filteredNotifSet(showPulsing = false))
-            keyguardViewStateRepository.setNotificationsFullyHidden(false)
+            notificationsKeyguardInteractor.setNotificationsFullyHidden(false)
             assertThat(filteredSet).comparingElementsUsing(byIsPulsing).doesNotContain(true)
         }
 
@@ -144,7 +144,7 @@
     fun filteredEntrySet_noPulsing_notifsFullyHidden() =
         testComponent.runTest {
             val filteredSet by collectLastValue(underTest.filteredNotifSet(showPulsing = false))
-            keyguardViewStateRepository.setNotificationsFullyHidden(true)
+            notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
             assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
         }
 }
@@ -161,7 +161,7 @@
 
         val activeNotificationListRepository: ActiveNotificationListRepository
         val deviceEntryRepository: FakeDeviceEntryRepository
-        val keyguardViewStateRepository: FakeNotificationsKeyguardViewStateRepository
+        val notificationsKeyguardInteractor: NotificationsKeyguardInteractor
 
         @Component.Factory
         interface Factory {
@@ -222,7 +222,7 @@
         testComponent.runTest {
             val filteredSet by collectLastValue(underTest.aodNotifs)
             deviceEntryRepository.setBypassEnabled(false)
-            keyguardViewStateRepository.setNotificationsFullyHidden(false)
+            notificationsKeyguardInteractor.setNotificationsFullyHidden(false)
             assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
         }
 
@@ -231,7 +231,7 @@
         testComponent.runTest {
             val filteredSet by collectLastValue(underTest.aodNotifs)
             deviceEntryRepository.setBypassEnabled(false)
-            keyguardViewStateRepository.setNotificationsFullyHidden(true)
+            notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
             assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
         }
 
@@ -240,7 +240,7 @@
         testComponent.runTest {
             val filteredSet by collectLastValue(underTest.aodNotifs)
             deviceEntryRepository.setBypassEnabled(true)
-            keyguardViewStateRepository.setNotificationsFullyHidden(false)
+            notificationsKeyguardInteractor.setNotificationsFullyHidden(false)
             assertThat(filteredSet).comparingElementsUsing(byIsPulsing).doesNotContain(true)
         }
 
@@ -249,7 +249,7 @@
         testComponent.runTest {
             val filteredSet by collectLastValue(underTest.aodNotifs)
             deviceEntryRepository.setBypassEnabled(true)
-            keyguardViewStateRepository.setNotificationsFullyHidden(true)
+            notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
             assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
         }
 }
@@ -266,7 +266,7 @@
 
         val activeNotificationListRepository: ActiveNotificationListRepository
         val headsUpIconsInteractor: HeadsUpNotificationIconInteractor
-        val keyguardViewStateRepository: FakeNotificationsKeyguardViewStateRepository
+        val notificationsKeyguardInteractor: NotificationsKeyguardInteractor
         val notificationListenerSettingsRepository: NotificationListenerSettingsRepository
 
         @Component.Factory
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java
index db0139c..55c49ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java
@@ -24,6 +24,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -41,6 +42,7 @@
     private static final int MAX_RETRIES = 5;
     private static final int RETRY_DELAY_MS = 1000;
     private static final int CONNECTION_MIN_DURATION_MS = 5000;
+    private static final String DUMPSYS_NAME = "dumpsys_name";
 
     private FakeSystemClock mFakeClock = new FakeSystemClock();
     private FakeExecutor mFakeExecutor = new FakeExecutor(mFakeClock);
@@ -49,8 +51,14 @@
     private ObservableServiceConnection<Proxy> mConnection;
 
     @Mock
+    private ObservableServiceConnection.Callback<Proxy> mConnectionCallback;
+
+    @Mock
     private Observer mObserver;
 
+    @Mock
+    private DumpManager mDumpManager;
+
     private static class Proxy {
     }
 
@@ -63,6 +71,8 @@
         mConnectionManager = new PersistentConnectionManager<>(
                 mFakeClock,
                 mFakeExecutor,
+                mDumpManager,
+                DUMPSYS_NAME,
                 mConnection,
                 MAX_RETRIES,
                 RETRY_DELAY_MS,
@@ -154,4 +164,16 @@
         callbackCaptor.getValue().onSourceChanged();
         verify(mConnection).bind();
     }
+
+    @Test
+    public void testAddConnectionCallback() {
+        mConnectionManager.addConnectionCallback(mConnectionCallback);
+        verify(mConnection).addCallback(mConnectionCallback);
+    }
+
+    @Test
+    public void testRemoveConnectionCallback() {
+        mConnectionManager.removeConnectionCallback(mConnectionCallback);
+        verify(mConnection).removeCallback(mConnectionCallback);
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index c6f12e2..397dc1a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -1,15 +1,38 @@
 package com.android.systemui.communal.data.repository
 
+import android.content.ComponentName
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.launch
 
 /** Fake implementation of [CommunalWidgetRepository] */
-class FakeCommunalWidgetRepository : CommunalWidgetRepository {
+class FakeCommunalWidgetRepository(private val coroutineScope: CoroutineScope) :
+    CommunalWidgetRepository {
     private val _communalWidgets = MutableStateFlow<List<CommunalWidgetContentModel>>(emptyList())
     override val communalWidgets: Flow<List<CommunalWidgetContentModel>> = _communalWidgets
+    private val _widgetAdded = MutableSharedFlow<Int>()
+    val widgetAdded: Flow<Int> = _widgetAdded
+
+    private var nextWidgetId = 1
 
     fun setCommunalWidgets(inventory: List<CommunalWidgetContentModel>) {
         _communalWidgets.value = inventory
     }
+
+    override fun addWidget(
+        provider: ComponentName,
+        priority: Int,
+        configureWidget: suspend (id: Int) -> Boolean
+    ) {
+        coroutineScope.launch {
+            val id = nextWidgetId++
+            if (configureWidget.invoke(id)) {
+                _widgetAdded.emit(id)
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt
index faacce6..eb287ee 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt
@@ -36,7 +36,8 @@
     fun create(
         testScope: TestScope = TestScope(),
         communalRepository: FakeCommunalRepository = FakeCommunalRepository(),
-        widgetRepository: FakeCommunalWidgetRepository = FakeCommunalWidgetRepository(),
+        widgetRepository: FakeCommunalWidgetRepository =
+            FakeCommunalWidgetRepository(testScope.backgroundScope),
         mediaRepository: FakeCommunalMediaRepository = FakeCommunalMediaRepository(),
         smartspaceRepository: FakeSmartspaceRepository = FakeSmartspaceRepository(),
         tutorialRepository: FakeCommunalTutorialRepository = FakeCommunalTutorialRepository(),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/FakeStatusBarNotificationsDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/FakeStatusBarNotificationsDataLayerModule.kt
index 788e3aa..1ffc9f4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/FakeStatusBarNotificationsDataLayerModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/FakeStatusBarNotificationsDataLayerModule.kt
@@ -15,8 +15,6 @@
  */
 package com.android.systemui.statusbar.notification.data
 
-import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardStateRepositoryModule
 import dagger.Module
 
-@Module(includes = [FakeNotificationsKeyguardStateRepositoryModule::class])
-object FakeStatusBarNotificationsDataLayerModule
+@Module(includes = []) object FakeStatusBarNotificationsDataLayerModule
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeNotificationsKeyguardViewStateRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeNotificationsKeyguardViewStateRepository.kt
deleted file mode 100644
index 5d3cb4d..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeNotificationsKeyguardViewStateRepository.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.statusbar.notification.data.repository
-
-import com.android.systemui.dagger.SysUISingleton
-import dagger.Binds
-import dagger.Module
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-
-@SysUISingleton
-class FakeNotificationsKeyguardViewStateRepository @Inject constructor() :
-    NotificationsKeyguardViewStateRepository {
-    private val _notificationsFullyHidden = MutableStateFlow(false)
-    override val areNotificationsFullyHidden: Flow<Boolean> = _notificationsFullyHidden
-
-    private val _isPulseExpanding = MutableStateFlow(false)
-    override val isPulseExpanding: Flow<Boolean> = _isPulseExpanding
-
-    fun setNotificationsFullyHidden(fullyHidden: Boolean) {
-        _notificationsFullyHidden.value = fullyHidden
-    }
-
-    fun setPulseExpanding(expanding: Boolean) {
-        _isPulseExpanding.value = expanding
-    }
-}
-
-@Module
-interface FakeNotificationsKeyguardStateRepositoryModule {
-    @Binds
-    fun bindFake(
-        fake: FakeNotificationsKeyguardViewStateRepository
-    ): NotificationsKeyguardViewStateRepository
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryKosmos.kt
index f2b9da4..df7fd94 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryKosmos.kt
@@ -18,7 +18,5 @@
 
 import com.android.systemui.kosmos.Kosmos
 
-var Kosmos.notificationsKeyguardViewStateRepository: NotificationsKeyguardViewStateRepository by
-    Kosmos.Fixture { fakeNotificationsKeyguardViewStateRepository }
-val Kosmos.fakeNotificationsKeyguardViewStateRepository by
-    Kosmos.Fixture { FakeNotificationsKeyguardViewStateRepository() }
+val Kosmos.notificationsKeyguardViewStateRepository: NotificationsKeyguardViewStateRepository by
+    Kosmos.Fixture { NotificationsKeyguardViewStateRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.kt
index 432464e..61a38b8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.kt
@@ -18,13 +18,11 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.statusbar.notification.data.repository.notificationsKeyguardViewStateRepository
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
 
 val Kosmos.notificationsKeyguardInteractor by Fixture {
     NotificationsKeyguardInteractor(
         repository = notificationsKeyguardViewStateRepository,
-        backgroundDispatcher = testDispatcher,
     )
 }
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 513c095..d967874 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -142,6 +142,11 @@
                     Assume.assumeFalse(IS_UNDER_RAVENWOOD);
                 }
 
+                // Stopgap for http://g/ravenwood/EPAD-N5ntxM
+                if (description.getMethodName().endsWith("$noRavenwood")) {
+                    Assume.assumeFalse(IS_UNDER_RAVENWOOD);
+                }
+
                 RavenwoodRuleImpl.init(RavenwoodRule.this);
                 try {
                     base.evaluate();
diff --git a/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java b/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java
index 085c186..7abfecf 100644
--- a/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java
+++ b/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java
@@ -42,4 +42,9 @@
     public void testIgnored() {
         throw new RuntimeException("Shouldn't be executed under ravenwood");
     }
+
+    @Test
+    public void testIgnored$noRavenwood() {
+        throw new RuntimeException("Shouldn't be executed under ravenwood");
+    }
 }
diff --git a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
index 553ba12..7fc1738 100644
--- a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
+++ b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
@@ -16,6 +16,7 @@
 
 package com.android.server.autofill;
 
+import static com.android.server.autofill.Session.REQUEST_ID_KEY;
 import static com.android.server.autofill.Session.SESSION_ID_KEY;
 
 import android.annotation.NonNull;
@@ -107,15 +108,16 @@
         mLastFlag = flag;
         if (mRemoteFillService != null && mRemoteFillService.isCredentialAutofillService()) {
             Slog.v(TAG, "About to call CredAutofill service as secondary provider");
-            addSessionIdToClientState(pendingFillRequest, pendingInlineSuggestionsRequest, id);
+            addSessionIdAndRequestIdToClientState(pendingFillRequest,
+                    pendingInlineSuggestionsRequest, id);
             mRemoteFillService.onFillCredentialRequest(pendingFillRequest, client);
         } else {
             mRemoteFillService.onFillRequest(pendingFillRequest);
         }
     }
 
-    private FillRequest addSessionIdToClientState(FillRequest pendingFillRequest,
-            InlineSuggestionsRequest pendingInlineSuggestionsRequest, int id) {
+    private FillRequest addSessionIdAndRequestIdToClientState(FillRequest pendingFillRequest,
+            InlineSuggestionsRequest pendingInlineSuggestionsRequest, int sessionId) {
         if (pendingFillRequest.getClientState() == null) {
             pendingFillRequest = new FillRequest(pendingFillRequest.getId(),
                     pendingFillRequest.getFillContexts(),
@@ -125,7 +127,8 @@
                     pendingInlineSuggestionsRequest,
                     pendingFillRequest.getDelayedFillIntentSender());
         }
-        pendingFillRequest.getClientState().putInt(SESSION_ID_KEY, id);
+        pendingFillRequest.getClientState().putInt(SESSION_ID_KEY, sessionId);
+        pendingFillRequest.getClientState().putInt(REQUEST_ID_KEY, pendingFillRequest.getId());
         return pendingFillRequest;
     }
 
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 007be05..6a81425 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -234,7 +234,8 @@
             new ComponentName("com.android.credentialmanager",
                     "com.android.credentialmanager.autofill.CredentialAutofillService");
 
-    static final String SESSION_ID_KEY = "session_id";
+    static final String SESSION_ID_KEY = "autofill_session_id";
+    static final String REQUEST_ID_KEY = "autofill_request_id";
 
     final Object mLock;
 
@@ -729,7 +730,7 @@
                         mPendingFillRequest.getFlags(), id, mClient);
             } else if (mRemoteFillService != null) {
                 if (mIsPrimaryCredential) {
-                    mPendingFillRequest = addSessionIdToClientState(mPendingFillRequest,
+                    mPendingFillRequest = addSessionIdAndRequestIdToClientState(mPendingFillRequest,
                             mPendingInlineSuggestionsRequest, id);
                     mRemoteFillService.onFillCredentialRequest(mPendingFillRequest, mClient);
                 } else {
@@ -877,8 +878,8 @@
         }
     }
 
-    private FillRequest addSessionIdToClientState(FillRequest pendingFillRequest,
-            InlineSuggestionsRequest pendingInlineSuggestionsRequest, int id) {
+    private FillRequest addSessionIdAndRequestIdToClientState(FillRequest pendingFillRequest,
+            InlineSuggestionsRequest pendingInlineSuggestionsRequest, int sessionId) {
         if (pendingFillRequest.getClientState() == null) {
             pendingFillRequest = new FillRequest(pendingFillRequest.getId(),
                     pendingFillRequest.getFillContexts(),
@@ -888,7 +889,8 @@
                     pendingInlineSuggestionsRequest,
                     pendingFillRequest.getDelayedFillIntentSender());
         }
-        pendingFillRequest.getClientState().putInt(SESSION_ID_KEY, id);
+        pendingFillRequest.getClientState().putInt(SESSION_ID_KEY, sessionId);
+        pendingFillRequest.getClientState().putInt(REQUEST_ID_KEY, pendingFillRequest.getId());
         return pendingFillRequest;
     }
 
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 19a9239..7a4ac6a 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1356,8 +1356,8 @@
 
         final int flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
         for (UserInfo user : users) {
-            prepareUserStorageInternal(fromVolumeUuid, user.id, user.serialNumber, flags);
-            prepareUserStorageInternal(toVolumeUuid, user.id, user.serialNumber, flags);
+            prepareUserStorageInternal(fromVolumeUuid, user.id, flags);
+            prepareUserStorageInternal(toVolumeUuid, user.id, flags);
         }
     }
 
@@ -3231,7 +3231,7 @@
 
     @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL)
     @Override
-    public void createUserStorageKeys(int userId, int serialNumber, boolean ephemeral) {
+    public void createUserStorageKeys(int userId, boolean ephemeral) {
 
         super.createUserStorageKeys_enforcePermission();
 
@@ -3276,8 +3276,7 @@
     /* Only for use by LockSettingsService */
     @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL)
     @Override
-    public void unlockCeStorage(@UserIdInt int userId, int serialNumber, byte[] secret)
-            throws RemoteException {
+    public void unlockCeStorage(@UserIdInt int userId, byte[] secret) throws RemoteException {
         super.unlockCeStorage_enforcePermission();
 
         if (StorageManager.isFileEncrypted()) {
@@ -3348,25 +3347,25 @@
                 continue;
             }
 
-            prepareUserStorageInternal(vol.fsUuid, user.id, user.serialNumber, flags);
+            prepareUserStorageInternal(vol.fsUuid, user.id, flags);
         }
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL)
     @Override
-    public void prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags) {
+    public void prepareUserStorage(String volumeUuid, int userId, int flags) {
 
         super.prepareUserStorage_enforcePermission();
 
         try {
-            prepareUserStorageInternal(volumeUuid, userId, serialNumber, flags);
+            prepareUserStorageInternal(volumeUuid, userId, flags);
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
     }
 
-    private void prepareUserStorageInternal(String volumeUuid, int userId, int serialNumber,
-            int flags) throws Exception {
+    private void prepareUserStorageInternal(String volumeUuid, int userId, int flags)
+            throws Exception {
         try {
             mVold.prepareUserStorage(volumeUuid, userId, flags);
             // After preparing user storage, we should check if we should mount data mirror again,
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 72e62c3..8ad60e6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -243,7 +243,7 @@
     /**
      * The default value to {@link #KEY_ENABLE_NEW_OOMADJ}.
      */
-    private static final boolean DEFAULT_ENABLE_NEW_OOM_ADJ = false;
+    private static final boolean DEFAULT_ENABLE_NEW_OOM_ADJ = Flags.oomadjusterCorrectnessRewrite();
 
     /**
      * Same as {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index f49e25a..ef7a0e0 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1385,6 +1385,8 @@
                         break;
                 }
 
+                // TODO: b/319163103 - limit isolated/sandbox trimming to just the processes
+                //  evaluated in the current update.
                 if (app.isolated && psr.numberOfRunningServices() <= 0
                         && app.getIsolatedEntryPoint() == null) {
                     // If this is an isolated process, there are no services
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index 7cc7c51..5a3fbe9 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -724,24 +724,13 @@
 
         if (fullUpdate) {
             assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
-            postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime);
         } else {
             activeProcesses.clear();
             activeProcesses.addAll(targetProcesses);
             assignCachedAdjIfNecessary(activeProcesses);
-
-            for (int  i = activeUids.size() - 1; i >= 0; i--) {
-                final UidRecord uidRec = activeUids.valueAt(i);
-                uidRec.forEachProcess(this::updateAppUidRecIfNecessaryLSP);
-            }
-            updateUidsLSP(activeUids, nowElapsed);
-
-            for (int i = 0, size = targetProcesses.size(); i < size; i++) {
-                applyOomAdjLSP(targetProcesses.valueAt(i), false, now, nowElapsed, oomAdjReason);
-            }
-
             activeProcesses.clear();
         }
+        postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime);
         targetProcesses.clear();
 
         if (startProfiling) {
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 08b129e..2771572 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -39,7 +39,7 @@
 import android.app.RemoteServiceException.CannotPostForegroundServiceNotificationException;
 import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
-import android.compat.annotation.Disabled;
+import android.compat.annotation.EnabledAfter;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -49,6 +49,7 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
+import android.os.Build.VERSION_CODES;
 import android.os.IBinder;
 import android.os.PowerExemptionManager;
 import android.os.SystemClock;
@@ -94,16 +95,14 @@
      * (See also android.app.ForegroundServiceTypePolicy)
      */
     @ChangeId
-    // @EnabledAfter(targetSdkVersion = VERSION_CODES.UPSIDE_DOWN_CAKE)
-    @Disabled
+    @EnabledAfter(targetSdkVersion = VERSION_CODES.UPSIDE_DOWN_CAKE)
     static final long USE_NEW_WIU_LOGIC_FOR_START = 311208629L;
 
     /**
      * Compat ID to enable the new FGS start logic, for capability calculation.
      */
     @ChangeId
-    // Always enabled
-    @Disabled
+    @EnabledAfter(targetSdkVersion = VERSION_CODES.UPSIDE_DOWN_CAKE)
     static final long USE_NEW_WIU_LOGIC_FOR_CAPABILITIES = 313677553L;
 
     /**
@@ -111,8 +110,7 @@
      * the background.
      */
     @ChangeId
-    // @EnabledAfter(targetSdkVersion = VERSION_CODES.UPSIDE_DOWN_CAKE)
-    @Disabled
+    @EnabledAfter(targetSdkVersion = VERSION_CODES.UPSIDE_DOWN_CAKE)
     static final long USE_NEW_BFSL_LOGIC = 311208749L;
 
     final ActivityManagerService ams;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index a1b6f29..91d533c 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -13591,6 +13591,46 @@
         }
     }
 
+
+    /**
+     * @see AudioManager#shouldNotificationSoundPlay(AudioAttributes)
+     */
+    @android.annotation.EnforcePermission(
+            android.Manifest.permission.QUERY_AUDIO_STATE)
+    public boolean shouldNotificationSoundPlay(@NonNull final AudioAttributes aa) {
+        super.shouldNotificationSoundPlay_enforcePermission();
+        Objects.requireNonNull(aa);
+
+        // don't play notifications if the stream volume associated with the
+        // AudioAttributes of the notification record is 0 (non-zero volume implies
+        // not silenced by SILENT or VIBRATE ringer mode)
+        final int stream = AudioAttributes.toLegacyStreamType(aa);
+        final boolean mutingFromVolume = getStreamVolume(stream) == 0;
+        if (mutingFromVolume) {
+            if (DEBUG_VOL) {
+                Slog.d(TAG, "notification should not play due to muted stream " + stream);
+            }
+            return false;
+        }
+
+        // don't play notifications if there is a user of GAIN_TRANSIENT_EXCLUSIVE audio focus
+        // and the focus owner is recording
+        final int uid = mMediaFocusControl.getExclusiveFocusOwnerUid();
+        if (uid == -1) { // return value is -1 if focus isn't GAIN_TRANSIENT_EXCLUSIVE
+            return true;
+        }
+        // is the owner of GAIN_TRANSIENT_EXCLUSIVE focus also recording?
+        final boolean mutingFromFocusAndRecording = mRecordMonitor.isRecordingActiveForUid(uid);
+        if (mutingFromFocusAndRecording) {
+            if (DEBUG_VOL) {
+                Slog.d(TAG, "notification should not play due to exclusive focus owner recording "
+                        + " uid:" + uid);
+            }
+            return false;
+        }
+        return true;
+    }
+
     //======================
     // Audioserver state dispatch
     //======================
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index 0df0006..1376bde 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -297,6 +297,23 @@
     }
 
     /**
+     * Return the UID of the focus owner that has focus with exclusive focus gain
+     * @return -1 if nobody has exclusive focus, the UID of the owner otherwise
+     */
+    protected int getExclusiveFocusOwnerUid() {
+        synchronized (mAudioFocusLock) {
+            if (mFocusStack.empty()) {
+                return -1;
+            }
+            final FocusRequester owner = mFocusStack.peek();
+            if (owner.getGainRequest() != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE) {
+                return -1;
+            }
+            return owner.getClientUid();
+        }
+    }
+
+    /**
      * Send AUDIOFOCUS_LOSS to a specific stack entry.
      * Note this method is supporting an external API, and is restricted to LOSS in order to
      * prevent allowing the stack to be in an invalid state (e.g. entry inside stack has focus)
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index c72632f..c2bc1e4 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -893,7 +893,7 @@
             if (AudioService.mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC
                     && safeDevicesContains(device)) {
                 soundDose.updateAttenuation(
-                        AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_MUSIC,
+                        -AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_MUSIC,
                                 (newIndex + 5) / 10,
                                 device), device);
             }
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index 89b638b..89e08c1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics.sensors;
 
+import static com.android.server.biometrics.sensors.BiometricSchedulerOperation.STATE_STARTED;
+
 import android.annotation.IntDef;
 import android.annotation.MainThread;
 import android.annotation.NonNull;
@@ -28,6 +30,7 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
@@ -35,6 +38,7 @@
 import com.android.modules.expresslog.Counter;
 import com.android.server.biometrics.BiometricSchedulerProto;
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
 
 import java.io.PrintWriter;
@@ -48,6 +52,7 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 
 /**
  * A scheduler for biometric HAL operations. Maintains a queue of {@link BaseClientMonitor}
@@ -56,11 +61,16 @@
  *
  * We currently assume (and require) that each biometric sensor have its own instance of a
  * {@link BiometricScheduler}.
+ *
+ * @param <T> Hal instance for starting the user.
+ * @param <U> Session associated with the current user id.
+ *
+ * TODO: (b/304604965) Update thread annotation when FLAGS_DE_HIDL is removed.
  */
 @MainThread
-public class BiometricScheduler {
+public class BiometricScheduler<T, U> {
 
-    private static final String BASE_TAG = "BiometricScheduler";
+    private static final String TAG = "BiometricScheduler";
     // Number of recent operations to keep in our logs for dumpsys
     protected static final int LOG_NUM_RECENT_OPERATIONS = 50;
 
@@ -89,30 +99,6 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface SensorType {}
 
-    public static @SensorType int sensorTypeFromFingerprintProperties(
-            @NonNull FingerprintSensorPropertiesInternal props) {
-        if (props.isAnyUdfpsType()) {
-            return SENSOR_TYPE_UDFPS;
-        }
-
-        return SENSOR_TYPE_FP_OTHER;
-    }
-
-    public static String sensorTypeToString(@SensorType int sensorType) {
-        switch (sensorType) {
-            case SENSOR_TYPE_UNKNOWN:
-                return "Unknown";
-            case SENSOR_TYPE_FACE:
-                return "Face";
-            case SENSOR_TYPE_UDFPS:
-                return "Udfps";
-            case SENSOR_TYPE_FP_OTHER:
-                return "OtherFp";
-            default:
-                return "UnknownUnknown";
-        }
-    }
-
     private static final class CrashState {
         static final int NUM_ENTRIES = 10;
         final String timestamp;
@@ -145,8 +131,8 @@
         }
     }
 
-    @NonNull protected final String mBiometricTag;
-    private final @SensorType int mSensorType;
+    @SensorType
+    private final int mSensorType;
     @Nullable private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
     @NonNull private final IBiometricService mBiometricService;
     @NonNull protected final Handler mHandler;
@@ -157,6 +143,43 @@
     private int mTotalOperationsHandled;
     private final int mRecentOperationsLimit;
     @NonNull private final List<Integer> mRecentOperations;
+    @Nullable private StopUserClient<U> mStopUserClient;
+    @NonNull private Supplier<Integer> mCurrentUserRetriever;
+    @Nullable private UserSwitchProvider<T, U> mUserSwitchProvider;
+
+    private class UserSwitchClientCallback implements ClientMonitorCallback {
+        @NonNull private final BaseClientMonitor mOwner;
+
+        UserSwitchClientCallback(@NonNull BaseClientMonitor owner) {
+            mOwner = owner;
+        }
+
+        @Override
+        public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
+            mHandler.post(() -> {
+                Slog.d(TAG, "[Client finished] " + clientMonitor + ", success: " + success);
+
+                // Set mStopUserClient to null when StopUserClient fails. Otherwise it's possible
+                // for that the queue will wait indefinitely until the field is cleared.
+                if (clientMonitor instanceof StopUserClient<?>) {
+                    if (!success) {
+                        Slog.w(TAG, "StopUserClient failed(), is the HAL stuck? "
+                                + "Clearing mStopUserClient");
+                    }
+                    mStopUserClient = null;
+                }
+                if (mCurrentOperation != null && mCurrentOperation.isFor(mOwner)) {
+                    mCurrentOperation = null;
+                } else {
+                    // can happen if the hal dies and is usually okay
+                    // do not unset the current operation that may be newer
+                    Slog.w(TAG, "operation is already null or different (reset?): "
+                            + mCurrentOperation);
+                }
+                startNextOperationIfIdle();
+            });
+        }
+    }
 
     // Internal callback, notified when an operation is complete. Notifies the requester
     // that the operation is complete, before performing internal scheduler work (such as
@@ -164,26 +187,26 @@
     private final ClientMonitorCallback mInternalCallback = new ClientMonitorCallback() {
         @Override
         public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
-            Slog.d(getTag(), "[Started] " + clientMonitor);
+            Slog.d(TAG, "[Started] " + clientMonitor);
         }
 
         @Override
         public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
             mHandler.post(() -> {
                 if (mCurrentOperation == null) {
-                    Slog.e(getTag(), "[Finishing] " + clientMonitor
+                    Slog.e(TAG, "[Finishing] " + clientMonitor
                             + " but current operation is null, success: " + success
                             + ", possible lifecycle bug in clientMonitor implementation?");
                     return;
                 }
 
                 if (!mCurrentOperation.isFor(clientMonitor)) {
-                    Slog.e(getTag(), "[Ignoring Finish] " + clientMonitor + " does not match"
+                    Slog.e(TAG, "[Ignoring Finish] " + clientMonitor + " does not match"
                             + " current: " + mCurrentOperation);
                     return;
                 }
 
-                Slog.d(getTag(), "[Finishing] " + clientMonitor + ", success: " + success);
+                Slog.d(TAG, "[Finishing] " + clientMonitor + ", success: " + success);
 
                 if (mGestureAvailabilityDispatcher != null) {
                     mGestureAvailabilityDispatcher.markSensorActive(
@@ -202,13 +225,11 @@
     };
 
     @VisibleForTesting
-    public BiometricScheduler(@NonNull String tag,
-            @NonNull Handler handler,
+    public BiometricScheduler(@NonNull Handler handler,
             @SensorType int sensorType,
             @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
             @NonNull IBiometricService biometricService,
             int recentOperationsLimit) {
-        mBiometricTag = tag;
         mHandler = handler;
         mSensorType = sensorType;
         mGestureAvailabilityDispatcher = gestureAvailabilityDispatcher;
@@ -219,49 +240,140 @@
         mRecentOperations = new ArrayList<>();
     }
 
+    @VisibleForTesting
+    public BiometricScheduler(@NonNull Handler handler,
+            @SensorType int sensorType,
+            @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            @NonNull IBiometricService biometricService,
+            int recentOperationsLimit,
+            @NonNull Supplier<Integer> currentUserRetriever,
+            @Nullable UserSwitchProvider<T, U> userSwitchProvider) {
+        mHandler = handler;
+        mSensorType = sensorType;
+        mGestureAvailabilityDispatcher = gestureAvailabilityDispatcher;
+        mPendingOperations = new ArrayDeque<>();
+        mBiometricService = biometricService;
+        mCrashStates = new ArrayDeque<>();
+        mRecentOperationsLimit = recentOperationsLimit;
+        mRecentOperations = new ArrayList<>();
+        mCurrentUserRetriever = currentUserRetriever;
+        mUserSwitchProvider = userSwitchProvider;
+    }
+
+    public BiometricScheduler(@NonNull Handler handler,
+            @SensorType int sensorType,
+            @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            @NonNull Supplier<Integer> currentUserRetriever,
+            @NonNull UserSwitchProvider<T, U> userSwitchProvider) {
+        this(handler, sensorType, gestureAvailabilityDispatcher,
+                IBiometricService.Stub.asInterface(ServiceManager.getService(
+                        Context.BIOMETRIC_SERVICE)), LOG_NUM_RECENT_OPERATIONS,
+                currentUserRetriever, userSwitchProvider);
+    }
+
     /**
      * Creates a new scheduler.
      *
-     * @param tag for the specific instance of the scheduler. Should be unique.
      * @param sensorType the sensorType that this scheduler is handling.
      * @param gestureAvailabilityDispatcher may be null if the sensor does not support gestures
      *                                      (such as fingerprint swipe).
      */
-    public BiometricScheduler(@NonNull String tag,
-            @SensorType int sensorType,
+    public BiometricScheduler(@SensorType int sensorType,
             @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
-        this(tag, new Handler(Looper.getMainLooper()), sensorType, gestureAvailabilityDispatcher,
+        this(new Handler(Looper.getMainLooper()), sensorType, gestureAvailabilityDispatcher,
                 IBiometricService.Stub.asInterface(
                         ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
                 LOG_NUM_RECENT_OPERATIONS);
     }
 
+    /**
+     * Returns sensor type for a fingerprint sensor.
+     */
+    @SensorType
+    public static int sensorTypeFromFingerprintProperties(
+            @NonNull FingerprintSensorPropertiesInternal props) {
+        if (props.isAnyUdfpsType()) {
+            return SENSOR_TYPE_UDFPS;
+        }
+
+        return SENSOR_TYPE_FP_OTHER;
+    }
+
     @VisibleForTesting
     public ClientMonitorCallback getInternalCallback() {
         return mInternalCallback;
     }
 
-    protected String getTag() {
-        return BASE_TAG + "/" + mBiometricTag;
+    protected void startNextOperationIfIdle() {
+        if (Flags.deHidl()) {
+            startNextOperation();
+        } else {
+            startNextOperationIfIdleLegacy();
+        }
     }
 
-    protected void startNextOperationIfIdle() {
+    protected void startNextOperation() {
         if (mCurrentOperation != null) {
-            Slog.v(getTag(), "Not idle, current operation: " + mCurrentOperation);
+            Slog.v(TAG, "Not idle, current operation: " + mCurrentOperation);
             return;
         }
         if (mPendingOperations.isEmpty()) {
-            Slog.d(getTag(), "No operations, returning to idle");
+            Slog.d(TAG, "No operations, returning to idle");
+            return;
+        }
+
+        final int currentUserId = mCurrentUserRetriever.get();
+        final int nextUserId = mPendingOperations.getFirst().getTargetUserId();
+
+        if (nextUserId == currentUserId || mPendingOperations.getFirst().isStartUserOperation()) {
+            startNextOperationIfIdleLegacy();
+        } else if (currentUserId == UserHandle.USER_NULL && mUserSwitchProvider != null) {
+            final BaseClientMonitor startClient =
+                    mUserSwitchProvider.getStartUserClient(nextUserId);
+            final UserSwitchClientCallback finishedCallback =
+                    new UserSwitchClientCallback(startClient);
+
+            Slog.d(TAG, "[Starting User] " + startClient);
+            mCurrentOperation = new BiometricSchedulerOperation(
+                    startClient, finishedCallback, STATE_STARTED);
+            startClient.start(finishedCallback);
+        } else if (mUserSwitchProvider != null) {
+            if (mStopUserClient != null) {
+                Slog.d(TAG, "[Waiting for StopUser] " + mStopUserClient);
+            } else {
+                mStopUserClient = mUserSwitchProvider
+                        .getStopUserClient(currentUserId);
+                final UserSwitchClientCallback finishedCallback =
+                        new UserSwitchClientCallback(mStopUserClient);
+
+                Slog.d(TAG, "[Stopping User] current: " + currentUserId
+                        + ", next: " + nextUserId + ". " + mStopUserClient);
+                mCurrentOperation = new BiometricSchedulerOperation(
+                        mStopUserClient, finishedCallback, STATE_STARTED);
+                mStopUserClient.start(finishedCallback);
+            }
+        } else {
+            Slog.e(TAG, "Cannot start next operation.");
+        }
+    }
+
+    protected void startNextOperationIfIdleLegacy() {
+        if (mCurrentOperation != null) {
+            Slog.v(TAG, "Not idle, current operation: " + mCurrentOperation);
+            return;
+        }
+        if (mPendingOperations.isEmpty()) {
+            Slog.d(TAG, "No operations, returning to idle");
             return;
         }
 
         mCurrentOperation = mPendingOperations.poll();
-        Slog.d(getTag(), "[Polled] " + mCurrentOperation);
+        Slog.d(TAG, "[Polled] " + mCurrentOperation);
 
         // If the operation at the front of the queue has been marked for cancellation, send
         // ERROR_CANCELED. No need to start this client.
         if (mCurrentOperation.isMarkedCanceling()) {
-            Slog.d(getTag(), "[Now Cancelling] " + mCurrentOperation);
+            Slog.d(TAG, "[Now Cancelling] " + mCurrentOperation);
             mCurrentOperation.cancel(mHandler, mInternalCallback);
             // Now we wait for the client to send its FinishCallback, which kicks off the next
             // operation.
@@ -289,7 +401,7 @@
                 // Note down current length of queue
                 final int pendingOperationsLength = mPendingOperations.size();
                 final BiometricSchedulerOperation lastOperation = mPendingOperations.peekLast();
-                Slog.e(getTag(), "[Unable To Start] " + mCurrentOperation
+                Slog.e(TAG, "[Unable To Start] " + mCurrentOperation
                         + ". Last pending operation: " + lastOperation);
 
                 // Then for each operation currently in the pending queue at the time of this
@@ -298,10 +410,10 @@
                 for (int i = 0; i < pendingOperationsLength; i++) {
                     final BiometricSchedulerOperation operation = mPendingOperations.pollFirst();
                     if (operation != null) {
-                        Slog.w(getTag(), "[Aborting Operation] " + operation);
+                        Slog.w(TAG, "[Aborting Operation] " + operation);
                         operation.abort();
                     } else {
-                        Slog.e(getTag(), "Null operation, index: " + i
+                        Slog.e(TAG, "Null operation, index: " + i
                                 + ", expected length: " + pendingOperationsLength);
                     }
                 }
@@ -317,9 +429,9 @@
                 mBiometricService.onReadyForAuthentication(
                         mCurrentOperation.getClientMonitor().getRequestId(), cookie);
             } catch (RemoteException e) {
-                Slog.e(getTag(), "Remote exception when contacting BiometricService", e);
+                Slog.e(TAG, "Remote exception when contacting BiometricService", e);
             }
-            Slog.d(getTag(), "Waiting for cookie before starting: " + mCurrentOperation);
+            Slog.d(TAG, "Waiting for cookie before starting: " + mCurrentOperation);
         }
     }
 
@@ -338,14 +450,14 @@
      */
     public void startPreparedClient(int cookie) {
         if (mCurrentOperation == null) {
-            Slog.e(getTag(), "Current operation is null");
+            Slog.e(TAG, "Current operation is null");
             return;
         }
 
         if (mCurrentOperation.startWithCookie(mInternalCallback, cookie)) {
-            Slog.d(getTag(), "[Started] Prepared client: " + mCurrentOperation);
+            Slog.d(TAG, "[Started] Prepared client: " + mCurrentOperation);
         } else {
-            Slog.e(getTag(), "[Unable To Start] Prepared client: " + mCurrentOperation);
+            Slog.e(TAG, "[Unable To Start] Prepared client: " + mCurrentOperation);
             mCurrentOperation = null;
             startNextOperationIfIdle();
         }
@@ -374,13 +486,13 @@
         if (clientMonitor.interruptsPrecedingClients()) {
             for (BiometricSchedulerOperation operation : mPendingOperations) {
                 if (operation.markCanceling()) {
-                    Slog.d(getTag(), "New client, marking pending op as canceling: " + operation);
+                    Slog.d(TAG, "New client, marking pending op as canceling: " + operation);
                 }
             }
         }
 
         mPendingOperations.add(new BiometricSchedulerOperation(clientMonitor, clientCallback));
-        Slog.d(getTag(), "[Added] " + clientMonitor
+        Slog.d(TAG, "[Added] " + clientMonitor
                 + ", new queue size: " + mPendingOperations.size());
 
         // If the new operation should interrupt preceding clients, and if the current operation is
@@ -389,7 +501,7 @@
                 && mCurrentOperation != null
                 && mCurrentOperation.isInterruptable()
                 && mCurrentOperation.isStarted()) {
-            Slog.d(getTag(), "[Cancelling Interruptable]: " + mCurrentOperation);
+            Slog.d(TAG, "[Cancelling Interruptable]: " + mCurrentOperation);
             mCurrentOperation.cancel(mHandler, mInternalCallback);
         } else {
             startNextOperationIfIdle();
@@ -401,16 +513,16 @@
      * @param token from the caller, should match the token passed in when requesting enrollment
      */
     public void cancelEnrollment(IBinder token, long requestId) {
-        Slog.d(getTag(), "cancelEnrollment, requestId: " + requestId);
+        Slog.d(TAG, "cancelEnrollment, requestId: " + requestId);
 
         if (mCurrentOperation != null
                 && canCancelEnrollOperation(mCurrentOperation, token, requestId)) {
-            Slog.d(getTag(), "Cancelling enrollment op: " + mCurrentOperation);
+            Slog.d(TAG, "Cancelling enrollment op: " + mCurrentOperation);
             mCurrentOperation.cancel(mHandler, mInternalCallback);
         } else {
             for (BiometricSchedulerOperation operation : mPendingOperations) {
                 if (canCancelEnrollOperation(operation, token, requestId)) {
-                    Slog.d(getTag(), "Cancelling pending enrollment op: " + operation);
+                    Slog.d(TAG, "Cancelling pending enrollment op: " + operation);
                     operation.markCanceling();
                 }
             }
@@ -423,16 +535,16 @@
      * @param requestId the id returned when requesting authentication
      */
     public void cancelAuthenticationOrDetection(IBinder token, long requestId) {
-        Slog.d(getTag(), "cancelAuthenticationOrDetection, requestId: " + requestId);
+        Slog.d(TAG, "cancelAuthenticationOrDetection, requestId: " + requestId);
 
         if (mCurrentOperation != null
                 && canCancelAuthOperation(mCurrentOperation, token, requestId)) {
-            Slog.d(getTag(), "Cancelling auth/detect op: " + mCurrentOperation);
+            Slog.d(TAG, "Cancelling auth/detect op: " + mCurrentOperation);
             mCurrentOperation.cancel(mHandler, mInternalCallback);
         } else {
             for (BiometricSchedulerOperation operation : mPendingOperations) {
                 if (canCancelAuthOperation(operation, token, requestId)) {
-                    Slog.d(getTag(), "Cancelling pending auth/detect op: " + operation);
+                    Slog.d(TAG, "Cancelling pending auth/detect op: " + operation);
                     operation.markCanceling();
                 }
             }
@@ -504,11 +616,11 @@
                 mCurrentOperation != null ? mCurrentOperation.toString() : null,
                 pendingOperations);
         mCrashStates.add(crashState);
-        Slog.e(getTag(), "Recorded crash state: " + crashState.toString());
+        Slog.e(TAG, "Recorded crash state: " + crashState.toString());
     }
 
     public void dump(PrintWriter pw) {
-        pw.println("Dump of BiometricScheduler " + getTag());
+        pw.println("Dump of BiometricScheduler " + TAG);
         pw.println("Type: " + mSensorType);
         pw.println("Current operation: " + mCurrentOperation);
         pw.println("Pending operations: " + mPendingOperations.size());
@@ -548,7 +660,7 @@
      * HAL dies.
      */
     public void reset() {
-        Slog.d(getTag(), "Resetting scheduler");
+        Slog.d(TAG, "Resetting scheduler");
         mPendingOperations.clear();
         mCurrentOperation = null;
     }
@@ -562,11 +674,11 @@
             return;
         }
         for (BiometricSchedulerOperation pendingOperation : mPendingOperations) {
-            Slog.d(getTag(), "[Watchdog cancelling pending] "
+            Slog.d(TAG, "[Watchdog cancelling pending] "
                     + pendingOperation.getClientMonitor());
             pendingOperation.markCancelingForWatchdog();
         }
-        Slog.d(getTag(), "[Watchdog cancelling current] "
+        Slog.d(TAG, "[Watchdog cancelling current] "
                 + mCurrentOperation.getClientMonitor());
         mCurrentOperation.cancel(mHandler, getInternalCallback());
     }
@@ -590,9 +702,23 @@
     /**
      * Handle stop user client when user switching occurs.
      */
-    public void onUserStopped() {}
+    public void onUserStopped() {
+        if (mStopUserClient == null) {
+            Slog.e(TAG, "Unexpected onUserStopped");
+            return;
+        }
+
+        Slog.d(TAG, "[OnUserStopped]: " + mStopUserClient);
+        mStopUserClient.onUserStopped();
+        mStopUserClient = null;
+    }
 
     public Handler getHandler() {
         return mHandler;
     }
+
+    @Nullable
+    public StopUserClient<?> getStopUserClient() {
+        return mStopUserClient;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
index e8654dc..e01c4ec 100644
--- a/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
@@ -30,7 +30,10 @@
 
 /**
  * Abstract class for stopping a user.
- * @param <T> Interface for stopping the user.
+ *
+ * @param <T> Session for stopping the user. It should be either an instance of
+ *            {@link com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession} or
+ *            {@link com.android.server.biometrics.sensors.face.aidl.AidlSession}.
  */
 public abstract class StopUserClient<T> extends HalClientMonitor<T> {
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
index 3753bbd..7ca10e3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
@@ -33,10 +33,14 @@
 
 /**
  * A user-aware scheduler that requests user-switches based on scheduled operation's targetUserId.
+ * TODO (b/304604965): Remove class when Flags.FLAG_DE_HIDL is removed.
+ *
+ * @param <T> Hal instance for starting the user.
+ * @param <U> Session associated with the current user id.
  */
-public class UserAwareBiometricScheduler extends BiometricScheduler {
+public class UserAwareBiometricScheduler<T, U> extends BiometricScheduler<T, U> {
 
-    private static final String BASE_TAG = "UaBiometricScheduler";
+    private static final String TAG = "UaBiometricScheduler";
 
     /**
      * Interface to retrieve the owner's notion of the current userId. Note that even though
@@ -66,13 +70,13 @@
         @Override
         public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
             mHandler.post(() -> {
-                Slog.d(getTag(), "[Client finished] " + clientMonitor + ", success: " + success);
+                Slog.d(TAG, "[Client finished] " + clientMonitor + ", success: " + success);
 
                 // Set mStopUserClient to null when StopUserClient fails. Otherwise it's possible
                 // for that the queue will wait indefinitely until the field is cleared.
                 if (clientMonitor instanceof StopUserClient<?>) {
                     if (!success) {
-                        Slog.w(getTag(), "StopUserClient failed(), is the HAL stuck? "
+                        Slog.w(TAG, "StopUserClient failed(), is the HAL stuck? "
                                 + "Clearing mStopUserClient");
                     }
                     mStopUserClient = null;
@@ -82,7 +86,7 @@
                 } else {
                     // can happen if the hal dies and is usually okay
                     // do not unset the current operation that may be newer
-                    Slog.w(getTag(), "operation is already null or different (reset?): "
+                    Slog.w(TAG, "operation is already null or different (reset?): "
                             + mCurrentOperation);
                 }
                 startNextOperationIfIdle();
@@ -98,7 +102,7 @@
             @NonNull IBiometricService biometricService,
             @NonNull CurrentUserRetriever currentUserRetriever,
             @NonNull UserSwitchCallback userSwitchCallback) {
-        super(tag, handler, sensorType, gestureAvailabilityDispatcher, biometricService,
+        super(handler, sensorType, gestureAvailabilityDispatcher, biometricService,
                 LOG_NUM_RECENT_OPERATIONS);
 
         mCurrentUserRetriever = currentUserRetriever;
@@ -117,18 +121,13 @@
     }
 
     @Override
-    protected String getTag() {
-        return BASE_TAG + "/" + mBiometricTag;
-    }
-
-    @Override
     protected void startNextOperationIfIdle() {
         if (mCurrentOperation != null) {
-            Slog.v(getTag(), "Not idle, current operation: " + mCurrentOperation);
+            Slog.v(TAG, "Not idle, current operation: " + mCurrentOperation);
             return;
         }
         if (mPendingOperations.isEmpty()) {
-            Slog.d(getTag(), "No operations, returning to idle");
+            Slog.d(TAG, "No operations, returning to idle");
             return;
         }
 
@@ -143,20 +142,20 @@
             final ClientFinishedCallback finishedCallback =
                     new ClientFinishedCallback(startClient);
 
-            Slog.d(getTag(), "[Starting User] " + startClient);
+            Slog.d(TAG, "[Starting User] " + startClient);
             mCurrentOperation = new BiometricSchedulerOperation(
                     startClient, finishedCallback, STATE_STARTED);
             startClient.start(finishedCallback);
         } else {
             if (mStopUserClient != null) {
-                Slog.d(getTag(), "[Waiting for StopUser] " + mStopUserClient);
+                Slog.d(TAG, "[Waiting for StopUser] " + mStopUserClient);
             } else {
                 mStopUserClient = mUserSwitchCallback
                         .getStopUserClient(currentUserId);
                 final ClientFinishedCallback finishedCallback =
                         new ClientFinishedCallback(mStopUserClient);
 
-                Slog.d(getTag(), "[Stopping User] current: " + currentUserId
+                Slog.d(TAG, "[Stopping User] current: " + currentUserId
                         + ", next: " + nextUserId + ". " + mStopUserClient);
                 mCurrentOperation = new BiometricSchedulerOperation(
                         mStopUserClient, finishedCallback, STATE_STARTED);
@@ -168,11 +167,11 @@
     @Override
     public void onUserStopped() {
         if (mStopUserClient == null) {
-            Slog.e(getTag(), "Unexpected onUserStopped");
+            Slog.e(TAG, "Unexpected onUserStopped");
             return;
         }
 
-        Slog.d(getTag(), "[OnUserStopped]: " + mStopUserClient);
+        Slog.d(TAG, "[OnUserStopped]: " + mStopUserClient);
         mStopUserClient.onUserStopped();
         mStopUserClient = null;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/UserSwitchProvider.java b/services/core/java/com/android/server/biometrics/sensors/UserSwitchProvider.java
new file mode 100644
index 0000000..bc5c55b
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/UserSwitchProvider.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors;
+
+import android.annotation.NonNull;
+
+/**
+ * Interface to get the appropriate start and stop user clients.
+ *
+ * @param <T> Hal instance for starting the user.
+ * @param <U> Session associated with the current user id.
+ */
+public interface UserSwitchProvider<T, U> {
+    @NonNull
+    StartUserClient<T, U> getStartUserClient(int newUserId);
+    @NonNull
+    StopUserClient<U> getStopUserClient(int userId);
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
index af46f44..3d61f99 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
@@ -53,12 +53,10 @@
         mAidlResponseHandler = aidlResponseHandler;
     }
 
-    /** The underlying {@link ISession}. */
     @NonNull public ISession getSession() {
         return mSession;
     }
 
-    /** The user id associated with the session. */
     public int getUserId() {
         return mUserId;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 9fa15b8..e4ecf1a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -39,6 +39,7 @@
 import android.hardware.face.IFaceServiceReceiver;
 import android.os.Binder;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
@@ -88,6 +89,8 @@
  * Provider for a single instance of the {@link IFace} HAL.
  */
 public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
+
+    private static final String TAG = "FaceProvider";
     private static final int ENROLL_TIMEOUT_SEC = 75;
 
     private boolean mTestHalEnabled;
@@ -159,7 +162,7 @@
             @NonNull BiometricContext biometricContext,
             boolean resetLockoutRequiresChallenge) {
         this(context, biometricStateCallback, props, halInstanceName, lockoutResetDispatcher,
-                biometricContext, null /* daemon */, resetLockoutRequiresChallenge,
+                biometricContext, null /* daemon */, getHandler(), resetLockoutRequiresChallenge,
                 false /* testHalEnabled */);
     }
 
@@ -169,13 +172,19 @@
             @NonNull String halInstanceName,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull BiometricContext biometricContext,
-            @Nullable IFace daemon, boolean resetLockoutRequiresChallenge,
+            @Nullable IFace daemon,
+            @NonNull Handler handler,
+            boolean resetLockoutRequiresChallenge,
             boolean testHalEnabled) {
         mContext = context;
         mBiometricStateCallback = biometricStateCallback;
         mHalInstanceName = halInstanceName;
         mFaceSensors = new SensorList<>(ActivityManager.getService());
-        mHandler = new Handler(Looper.getMainLooper());
+        if (Flags.deHidl()) {
+            mHandler = handler;
+        } else {
+            mHandler = new Handler(Looper.getMainLooper());
+        }
         mUsageStats = new UsageStats(context);
         mLockoutResetDispatcher = lockoutResetDispatcher;
         mActivityTaskManager = ActivityTaskManager.getInstance();
@@ -189,6 +198,13 @@
         initSensors(resetLockoutRequiresChallenge, props);
     }
 
+    @NonNull
+    private static Handler getHandler() {
+        HandlerThread handlerThread = new HandlerThread(TAG);
+        handlerThread.start();
+        return new Handler(handlerThread.getLooper());
+    }
+
     private void initAuthenticationBroadcastReceiver() {
         new AuthenticationStatsBroadcastReceiver(
                 mContext,
@@ -230,8 +246,8 @@
                         prop.commonProps.maxEnrollmentsPerUser, componentInfo, prop.sensorType,
                         prop.supportsDetectInteraction, prop.halControlsPreview,
                         false /* resetLockoutRequiresChallenge */);
-                final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this,
-                        mContext, mHandler, internalProp, mLockoutResetDispatcher,
+                final Sensor sensor = new Sensor(this,
+                        mContext, mHandler, internalProp,
                         mBiometricContext);
                 sensor.init(mLockoutResetDispatcher, this);
                 final int userId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
@@ -250,9 +266,8 @@
 
     private void addHidlSensors(SensorProps prop, boolean resetLockoutRequiresChallenge) {
         final int sensorId = prop.commonProps.sensorId;
-        final Sensor sensor = new HidlToAidlSensorAdapter(getTag() + "/" + sensorId, this,
-                mContext, mHandler, prop, mLockoutResetDispatcher,
-                mBiometricContext, resetLockoutRequiresChallenge,
+        final Sensor sensor = new HidlToAidlSensorAdapter(this, mContext, mHandler, prop,
+                mLockoutResetDispatcher, mBiometricContext, resetLockoutRequiresChallenge,
                 () -> {
                     //TODO: update to make this testable
                     scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser(),
@@ -279,8 +294,7 @@
 
     private void addAidlSensors(SensorProps prop, boolean resetLockoutRequiresChallenge) {
         final int sensorId = prop.commonProps.sensorId;
-        final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext,
-                mHandler, prop, mLockoutResetDispatcher, mBiometricContext,
+        final Sensor sensor = new Sensor(this, mContext, mHandler, prop, mBiometricContext,
                 resetLockoutRequiresChallenge);
         sensor.init(mLockoutResetDispatcher, this);
         final int userId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
@@ -296,7 +310,7 @@
     }
 
     private String getTag() {
-        return "FaceProvider/" + mHalInstanceName;
+        return TAG + "/" + mHalInstanceName;
     }
 
     boolean hasHalInstance() {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java
index 0110ae9..e5ae8e3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.hardware.biometrics.face.ISession;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -30,10 +31,10 @@
 
 import java.util.function.Supplier;
 
-public class FaceStopUserClient extends StopUserClient<AidlSession> {
+public class FaceStopUserClient extends StopUserClient<ISession> {
     private static final String TAG = "FaceStopUserClient";
 
-    public FaceStopUserClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
+    public FaceStopUserClient(@NonNull Context context, @NonNull Supplier<ISession> lazyDaemon,
             @Nullable IBinder token, int userId, int sensorId,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull UserStoppedCallback callback) {
@@ -49,7 +50,7 @@
     @Override
     protected void startHalOperation() {
         try {
-            getFreshDaemon().getSession().close();
+            getFreshDaemon().close();
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception", e);
             getCallback().onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index 3e5c599..635e79a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -58,6 +58,7 @@
 import com.android.server.biometrics.sensors.StartUserClient;
 import com.android.server.biometrics.sensors.StopUserClient;
 import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
+import com.android.server.biometrics.sensors.UserSwitchProvider;
 import com.android.server.biometrics.sensors.face.FaceUtils;
 
 import java.util.ArrayList;
@@ -71,15 +72,16 @@
  */
 public class Sensor {
 
+    private static final String TAG = "Sensor";
+
     private boolean mTestHalEnabled;
 
-    @NonNull private final String mTag;
     @NonNull private final FaceProvider mProvider;
     @NonNull private final Context mContext;
     @NonNull private final IBinder mToken;
     @NonNull private final Handler mHandler;
     @NonNull private final FaceSensorPropertiesInternal mSensorProperties;
-    @NonNull private BiometricScheduler mScheduler;
+    @NonNull private BiometricScheduler<IFace, ISession> mScheduler;
     @Nullable private LockoutTracker mLockoutTracker;
     @NonNull private final Map<Integer, Long> mAuthenticatorIds;
 
@@ -88,11 +90,9 @@
     @NonNull BiometricContext mBiometricContext;
 
 
-    Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
+    Sensor(@NonNull FaceProvider provider, @NonNull Context context,
             @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
-            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull BiometricContext biometricContext, @Nullable AidlSession session) {
-        mTag = tag;
+            @NonNull BiometricContext biometricContext) {
         mProvider = provider;
         mContext = context;
         mToken = new Binder();
@@ -102,105 +102,135 @@
         mAuthenticatorIds = new HashMap<>();
     }
 
-    Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
-            @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
-            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull BiometricContext biometricContext) {
-        this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher,
-                biometricContext, null);
-    }
-
-    public Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
+    public Sensor(@NonNull FaceProvider provider, @NonNull Context context,
             @NonNull Handler handler, @NonNull SensorProps prop,
-            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull BiometricContext biometricContext,
             boolean resetLockoutRequiresChallenge) {
-        this(tag, provider, context, handler,
+        this(provider, context, handler,
                 getFaceSensorPropertiesInternal(prop, resetLockoutRequiresChallenge),
-                lockoutResetDispatcher, biometricContext, null);
+                biometricContext);
     }
 
     /**
      * Initialize biometric scheduler, lockout tracker and session for the sensor.
      */
-    public void init(LockoutResetDispatcher lockoutResetDispatcher,
+    public void init(@NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull FaceProvider provider) {
+        if (Flags.deHidl()) {
+            setScheduler(getBiometricSchedulerForInit(lockoutResetDispatcher, provider));
+        } else {
+            setScheduler(getUserAwareBiometricSchedulerForInit(lockoutResetDispatcher, provider));
+        }
+        mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
+        mLockoutTracker = new LockoutCache();
+    }
+
+    private BiometricScheduler<IFace, ISession> getBiometricSchedulerForInit(
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull FaceProvider provider) {
+        return new BiometricScheduler<>(mHandler,
+                BiometricScheduler.SENSOR_TYPE_FACE,
+                null /* gestureAvailabilityDispatcher */,
+                () -> mCurrentSession != null ? mCurrentSession.getUserId() : UserHandle.USER_NULL,
+                new UserSwitchProvider<IFace, ISession>() {
+                    @NonNull
+                    @Override
+                    public StopUserClient<ISession> getStopUserClient(int userId) {
+                        return new FaceStopUserClient(mContext,
+                                () -> mLazySession.get().getSession(), mToken, userId,
+                                mSensorProperties.sensorId, BiometricLogger.ofUnknown(mContext),
+                                mBiometricContext, () -> mCurrentSession = null);
+                    }
+
+                    @NonNull
+                    @Override
+                    public StartUserClient<IFace, ISession> getStartUserClient(int newUserId) {
+                        final int sensorId = mSensorProperties.sensorId;
+                        final AidlResponseHandler resultController = new AidlResponseHandler(
+                                mContext, mScheduler, sensorId, newUserId,
+                                mLockoutTracker, lockoutResetDispatcher,
+                                mBiometricContext.getAuthSessionCoordinator(), () -> {
+                        },
+                                new AidlResponseHandler.AidlResponseHandlerCallback() {
+                                    @Override
+                                    public void onEnrollSuccess() {
+                                        mProvider.scheduleLoadAuthenticatorIdsForUser(sensorId,
+                                                newUserId);
+                                        mProvider.scheduleInvalidationRequest(sensorId,
+                                                newUserId);
+                                    }
+
+                                    @Override
+                                    public void onHardwareUnavailable() {
+                                        Slog.e(TAG, "Face sensor hardware unavailable.");
+                                        mCurrentSession = null;
+                                    }
+                                });
+
+                        return Sensor.this.getStartUserClient(resultController, sensorId,
+                                newUserId, provider);
+                    }
+                });
+    }
+
+    private UserAwareBiometricScheduler<IFace, ISession> getUserAwareBiometricSchedulerForInit(
+            LockoutResetDispatcher lockoutResetDispatcher,
             FaceProvider provider) {
-        mScheduler = new UserAwareBiometricScheduler(mTag,
+        return new UserAwareBiometricScheduler<>(TAG,
                 BiometricScheduler.SENSOR_TYPE_FACE, null /* gestureAvailabilityDispatcher */,
                 () -> mCurrentSession != null ? mCurrentSession.getUserId() : UserHandle.USER_NULL,
                 new UserAwareBiometricScheduler.UserSwitchCallback() {
                     @NonNull
                     @Override
-                    public StopUserClient<?> getStopUserClient(int userId) {
-                        return new FaceStopUserClient(mContext, mLazySession, mToken, userId,
-                                mSensorProperties.sensorId,
-                                BiometricLogger.ofUnknown(mContext), mBiometricContext,
-                                () -> mCurrentSession = null);
+                    public StopUserClient<ISession> getStopUserClient(int userId) {
+                        return new FaceStopUserClient(mContext,
+                                () -> mLazySession.get().getSession(), mToken, userId,
+                                mSensorProperties.sensorId, BiometricLogger.ofUnknown(mContext),
+                                mBiometricContext, () -> mCurrentSession = null);
                     }
 
                     @NonNull
                     @Override
-                    public StartUserClient<?, ?> getStartUserClient(int newUserId) {
+                    public StartUserClient<IFace, ISession> getStartUserClient(int newUserId) {
                         final int sensorId = mSensorProperties.sensorId;
+                        final AidlResponseHandler resultController = new AidlResponseHandler(
+                                mContext, mScheduler, sensorId, newUserId,
+                                mLockoutTracker, lockoutResetDispatcher,
+                                mBiometricContext.getAuthSessionCoordinator(), () -> {
+                                    Slog.e(TAG, "Face sensor hardware unavailable.");
+                                    mCurrentSession = null;
+                                });
 
-                        final AidlResponseHandler resultController;
-                        if (Flags.deHidl()) {
-                            resultController = new AidlResponseHandler(
-                                    mContext, mScheduler, sensorId, newUserId,
-                                    mLockoutTracker, lockoutResetDispatcher,
-                                    mBiometricContext.getAuthSessionCoordinator(), () -> {},
-                                    new AidlResponseHandler.AidlResponseHandlerCallback() {
-                                        @Override
-                                        public void onEnrollSuccess() {
-                                            mProvider.scheduleLoadAuthenticatorIdsForUser(sensorId,
-                                                    newUserId);
-                                            mProvider.scheduleInvalidationRequest(sensorId,
-                                                    newUserId);
-                                        }
-
-                                        @Override
-                                        public void onHardwareUnavailable() {
-                                            Slog.e(mTag, "Face sensor hardware unavailable.");
-                                            mCurrentSession = null;
-                                        }
-                                    });
-                        } else {
-                            resultController = new AidlResponseHandler(
-                                    mContext, mScheduler, sensorId, newUserId,
-                                    mLockoutTracker, lockoutResetDispatcher,
-                                    mBiometricContext.getAuthSessionCoordinator(), () -> {
-                                Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
-                                mCurrentSession = null;
-                            });
-                        }
-
-                        final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
-                                (userIdStarted, newSession, halInterfaceVersion) -> {
-                                    Slog.d(mTag, "New session created for user: "
-                                            + userIdStarted + " with hal version: "
-                                            + halInterfaceVersion);
-                                    mCurrentSession = new AidlSession(halInterfaceVersion,
-                                            newSession, userIdStarted, resultController);
-                                    if (FaceUtils.getLegacyInstance(sensorId)
-                                            .isInvalidationInProgress(mContext, userIdStarted)) {
-                                        Slog.w(mTag,
-                                                "Scheduling unfinished invalidation request for "
-                                                        + "sensor: "
-                                                        + sensorId
-                                                        + ", user: " + userIdStarted);
-                                        provider.scheduleInvalidationRequest(sensorId,
-                                                userIdStarted);
-                                    }
-                                };
-
-                        return new FaceStartUserClient(mContext, provider::getHalInstance,
-                                mToken, newUserId, mSensorProperties.sensorId,
-                                BiometricLogger.ofUnknown(mContext), mBiometricContext,
-                                resultController, userStartedCallback);
+                        return Sensor.this.getStartUserClient(resultController, sensorId,
+                                newUserId, provider);
                     }
                 });
-        mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
-        mLockoutTracker = new LockoutCache();
+    }
+
+    private FaceStartUserClient getStartUserClient(@NonNull AidlResponseHandler resultController,
+            int sensorId, int newUserId, @NonNull FaceProvider provider) {
+        final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
+                (userIdStarted, newSession, halInterfaceVersion) -> {
+                    Slog.d(TAG, "New face session created for user: "
+                            + userIdStarted + " with hal version: "
+                            + halInterfaceVersion);
+                    mCurrentSession = new AidlSession(halInterfaceVersion,
+                            newSession, userIdStarted, resultController);
+                    if (FaceUtils.getLegacyInstance(sensorId)
+                            .isInvalidationInProgress(mContext, userIdStarted)) {
+                        Slog.w(TAG,
+                                "Scheduling unfinished invalidation request for "
+                                        + "face sensor: "
+                                        + sensorId
+                                        + ", user: " + userIdStarted);
+                        provider.scheduleInvalidationRequest(sensorId,
+                                userIdStarted);
+                    }
+                };
+
+        return new FaceStartUserClient(mContext, provider::getHalInstance, mToken, newUserId,
+                mSensorProperties.sensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext,
+                resultController, userStartedCallback);
     }
 
     private static FaceSensorPropertiesInternal getFaceSensorPropertiesInternal(SensorProps prop,
@@ -213,13 +243,11 @@
                         info.softwareVersion));
             }
         }
-        final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
+        return new FaceSensorPropertiesInternal(
                 prop.commonProps.sensorId, prop.commonProps.sensorStrength,
                 prop.commonProps.maxEnrollmentsPerUser, componentInfo, prop.sensorType,
                 prop.supportsDetectInteraction, prop.halControlsPreview,
                 resetLockoutRequiresChallenge);
-
-        return internalProp;
     }
 
     @NonNull public Supplier<AidlSession> getLazySession() {
@@ -243,7 +271,7 @@
                 mProvider, this);
     }
 
-    @NonNull public BiometricScheduler getScheduler() {
+    @NonNull public BiometricScheduler<IFace, ISession> getScheduler() {
         return mScheduler;
     }
 
@@ -259,17 +287,17 @@
     }
 
     void setTestHalEnabled(boolean enabled) {
-        Slog.w(mTag, "setTestHalEnabled: " + enabled);
+        Slog.w(TAG, "Face setTestHalEnabled: " + enabled);
         if (enabled != mTestHalEnabled) {
             // The framework should retrieve a new session from the HAL.
             try {
                 if (mCurrentSession != null) {
                     // TODO(181984005): This should be scheduled instead of directly invoked
-                    Slog.d(mTag, "Closing old session");
+                    Slog.d(TAG, "Closing old face session");
                     mCurrentSession.getSession().close();
                 }
             } catch (RemoteException e) {
-                Slog.e(mTag, "RemoteException", e);
+                Slog.e(TAG, "RemoteException", e);
             }
             mCurrentSession = null;
         }
@@ -308,7 +336,7 @@
     public void onBinderDied() {
         final BaseClientMonitor client = mScheduler.getCurrentClient();
         if (client != null && client.isInterruptable()) {
-            Slog.e(mTag, "Sending ERROR_HW_UNAVAILABLE for client: " + client);
+            Slog.e(TAG, "Sending face hardware unavailable error for client: " + client);
             final ErrorConsumer errorConsumer = (ErrorConsumer) client;
             errorConsumer.onError(FaceManager.FACE_ERROR_HW_UNAVAILABLE,
                     0 /* vendorCode */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index 46ce0b6..5337666 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -120,7 +120,7 @@
     @NonNull private final FaceSensorPropertiesInternal mSensorProperties;
     @NonNull private final BiometricStateCallback mBiometricStateCallback;
     @NonNull private final Context mContext;
-    @NonNull private final BiometricScheduler mScheduler;
+    @NonNull private final BiometricScheduler<IBiometricsFace, AidlSession> mScheduler;
     @NonNull private final Handler mHandler;
     @NonNull private final Supplier<IBiometricsFace> mLazyDaemon;
     @NonNull private final LockoutHalImpl mLockoutTracker;
@@ -163,14 +163,15 @@
         private final int mSensorId;
         @NonNull private final Context mContext;
         @NonNull private final Handler mHandler;
-        @NonNull private final BiometricScheduler mScheduler;
+        @NonNull private final BiometricScheduler<IBiometricsFace, AidlSession> mScheduler;
         @Nullable private Callback mCallback;
         @NonNull private final LockoutHalImpl mLockoutTracker;
         @NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;
 
 
         HalResultController(int sensorId, @NonNull Context context, @NonNull Handler handler,
-                @NonNull BiometricScheduler scheduler, @NonNull LockoutHalImpl lockoutTracker,
+                @NonNull BiometricScheduler<IBiometricsFace, AidlSession> scheduler,
+                @NonNull LockoutHalImpl lockoutTracker,
                 @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
             mSensorId = sensorId;
             mContext = context;
@@ -352,7 +353,7 @@
             @NonNull FaceSensorPropertiesInternal sensorProps,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull Handler handler,
-            @NonNull BiometricScheduler scheduler,
+            @NonNull BiometricScheduler<IBiometricsFace, AidlSession> scheduler,
             @NonNull BiometricContext biometricContext) {
         mSensorProperties = sensorProps;
         mContext = context;
@@ -395,7 +396,8 @@
             @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
         final Handler handler = new Handler(Looper.getMainLooper());
         return new Face10(context, biometricStateCallback, sensorProps, lockoutResetDispatcher,
-                handler, new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_FACE,
+                handler, new BiometricScheduler<>(
+                        BiometricScheduler.SENSOR_TYPE_FACE,
                         null /* gestureAvailabilityTracker */),
                 BiometricContext.getInstance(context));
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
index 6355cb5..a004cae4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.UserInfo;
+import android.hardware.biometrics.face.ISession;
 import android.hardware.biometrics.face.SensorProps;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
 import android.os.Handler;
@@ -67,8 +68,7 @@
             };
     private LockoutHalImpl mLockoutTracker;
 
-    public HidlToAidlSensorAdapter(@NonNull String tag,
-            @NonNull FaceProvider provider,
+    public HidlToAidlSensorAdapter(@NonNull FaceProvider provider,
             @NonNull Context context,
             @NonNull Handler handler,
             @NonNull SensorProps prop,
@@ -76,15 +76,14 @@
             @NonNull BiometricContext biometricContext,
             boolean resetLockoutRequiresChallenge,
             @NonNull Runnable internalCleanupAndGetFeatureRunnable) {
-        this(tag, provider, context, handler, prop, lockoutResetDispatcher, biometricContext,
+        this(provider, context, handler, prop, lockoutResetDispatcher, biometricContext,
                 resetLockoutRequiresChallenge, internalCleanupAndGetFeatureRunnable,
                 new AuthSessionCoordinator(), null /* daemon */,
                 null /* onEnrollSuccessCallback */);
     }
 
     @VisibleForTesting
-    HidlToAidlSensorAdapter(@NonNull String tag,
-            @NonNull FaceProvider provider,
+    HidlToAidlSensorAdapter(@NonNull FaceProvider provider,
             @NonNull Context context,
             @NonNull Handler handler,
             @NonNull SensorProps prop,
@@ -95,7 +94,7 @@
             @NonNull AuthSessionCoordinator authSessionCoordinator,
             @Nullable IBiometricsFace daemon,
             @Nullable AidlResponseHandler.AidlResponseHandlerCallback aidlResponseHandlerCallback) {
-        super(tag, provider, context, handler, prop, lockoutResetDispatcher, biometricContext,
+        super(provider, context, handler, prop, biometricContext,
                 resetLockoutRequiresChallenge);
         mInternalCleanupAndGetFeatureRunnable = internalCleanupAndGetFeatureRunnable;
         mFaceProvider = provider;
@@ -124,7 +123,7 @@
 
     @Override
     public void serviceDied(long cookie) {
-        Slog.d(TAG, "HAL died.");
+        Slog.d(TAG, "Face HAL died.");
         mDaemon = null;
     }
 
@@ -140,10 +139,12 @@
     }
 
     @Override
-    public void init(LockoutResetDispatcher lockoutResetDispatcher,
-            FaceProvider provider) {
-        setScheduler(new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_FACE,
-                null /* gestureAvailabilityTracker */));
+    public void init(@NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull FaceProvider provider) {
+        setScheduler(new BiometricScheduler<ISession, AidlSession>(getHandler(),
+                BiometricScheduler.SENSOR_TYPE_FACE,
+                null /* gestureAvailabilityTracker */, () -> mCurrentUserId,
+                null /* userSwitchProvider */));
         setLazySession(this::getSession);
         mLockoutTracker = new LockoutHalImpl();
     }
@@ -188,7 +189,7 @@
             return mDaemon;
         }
 
-        Slog.d(TAG, "Daemon was null, reconnecting, current operation: "
+        Slog.d(TAG, "Face daemon was null, reconnecting, current operation: "
                 + getScheduler().getCurrentClient());
 
         try {
@@ -213,7 +214,7 @@
     }
 
     @VisibleForTesting void handleUserChanged(int newUserId) {
-        Slog.d(TAG, "User changed. Current user is " + newUserId);
+        Slog.d(TAG, "User changed. Current user for face sensor is " + newUserId);
         mSession = null;
         mCurrentUserId = newUserId;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java
index 5daf2d4..fa95361 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java
@@ -282,7 +282,7 @@
 
     @Override
     public ICancellationSignal enrollWithOptions(FaceEnrollOptions options) {
-        //Unsupported in HIDL
+        Slog.e(TAG, "enrollWithOptions unsupported in HIDL");
         return null;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
index 8ff105b..0d4dac0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
@@ -51,12 +51,10 @@
         mAidlResponseHandler = aidlResponseHandler;
     }
 
-    /** The underlying {@link ISession}. */
     @NonNull public ISession getSession() {
         return mSession;
     }
 
-    /** The user id associated with the session. */
     public int getUserId() {
         return mUserId;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 88a11d9..c0388d1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -46,6 +46,7 @@
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.Binder;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
@@ -102,6 +103,8 @@
 @SuppressWarnings("deprecation")
 public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvider {
 
+    private static final String TAG = "FingerprintProvider";
+
     private boolean mTestHalEnabled;
 
     @NonNull
@@ -172,7 +175,7 @@
             boolean resetLockoutRequiresHardwareAuthToken) {
         this(context, biometricStateCallback, authenticationStateListeners, props, halInstanceName,
                 lockoutResetDispatcher, gestureAvailabilityDispatcher, biometricContext,
-                null /* daemon */, resetLockoutRequiresHardwareAuthToken,
+                null /* daemon */, getHandler(), resetLockoutRequiresHardwareAuthToken,
                 false /* testHalEnabled */);
     }
 
@@ -184,6 +187,7 @@
             @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
             @NonNull BiometricContext biometricContext,
             @Nullable IFingerprint daemon,
+            @NonNull Handler handler,
             boolean resetLockoutRequiresHardwareAuthToken,
             boolean testHalEnabled) {
         mContext = context;
@@ -191,7 +195,11 @@
         mAuthenticationStateListeners = authenticationStateListeners;
         mHalInstanceName = halInstanceName;
         mFingerprintSensors = new SensorList<>(ActivityManager.getService());
-        mHandler = new Handler(Looper.getMainLooper());
+        if (Flags.deHidl()) {
+            mHandler = handler;
+        } else {
+            mHandler = new Handler(Looper.getMainLooper());
+        }
         mLockoutResetDispatcher = lockoutResetDispatcher;
         mActivityTaskManager = ActivityTaskManager.getInstance();
         mTaskStackListener = new BiometricTaskStackListener();
@@ -204,6 +212,13 @@
         initSensors(resetLockoutRequiresHardwareAuthToken, props, gestureAvailabilityDispatcher);
     }
 
+    @NonNull
+    private static Handler getHandler() {
+        HandlerThread handlerThread = new HandlerThread(TAG);
+        handlerThread.start();
+        return new Handler(handlerThread.getLooper());
+    }
+
     private void initAuthenticationBroadcastReceiver() {
         new AuthenticationStatsBroadcastReceiver(
                 mContext,
@@ -262,11 +277,9 @@
                                                                 location.sensorLocationY,
                                                                 location.sensorRadius))
                                                 .collect(Collectors.toList()));
-                final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext,
-                        mHandler, internalProp, mLockoutResetDispatcher,
-                        gestureAvailabilityDispatcher, mBiometricContext);
-                sensor.init(gestureAvailabilityDispatcher,
-                        mLockoutResetDispatcher);
+                final Sensor sensor = new Sensor(this, mContext, mHandler, internalProp,
+                        mBiometricContext);
+                sensor.init(gestureAvailabilityDispatcher, mLockoutResetDispatcher);
                 final int sessionUserId =
                         sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
                                 sensor.getLazySession().get().getUserId();
@@ -286,10 +299,8 @@
             @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
             boolean resetLockoutRequiresHardwareAuthToken) {
         final int sensorId = prop.commonProps.sensorId;
-        final Sensor sensor = new HidlToAidlSensorAdapter(getTag() + "/"
-                + sensorId, this, mContext, mHandler,
-                prop, mLockoutResetDispatcher, gestureAvailabilityDispatcher,
-                mBiometricContext, resetLockoutRequiresHardwareAuthToken,
+        final Sensor sensor = new HidlToAidlSensorAdapter(this, mContext, mHandler, prop,
+                mLockoutResetDispatcher, mBiometricContext, resetLockoutRequiresHardwareAuthToken,
                 () -> scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser(),
                         null /* callback */));
         sensor.init(gestureAvailabilityDispatcher, mLockoutResetDispatcher);
@@ -307,14 +318,11 @@
 
     private void addAidlSensors(@NonNull SensorProps prop,
             @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
-            List<SensorLocationInternal> workaroundLocations,
+            @NonNull List<SensorLocationInternal> workaroundLocations,
             boolean resetLockoutRequiresHardwareAuthToken) {
         final int sensorId = prop.commonProps.sensorId;
-        final Sensor sensor = new Sensor(getTag() + "/" + sensorId,
-                this, mContext, mHandler,
-                prop, mLockoutResetDispatcher, gestureAvailabilityDispatcher,
-                mBiometricContext, workaroundLocations,
-                resetLockoutRequiresHardwareAuthToken);
+        final Sensor sensor = new Sensor(this, mContext, mHandler, prop, mBiometricContext,
+                workaroundLocations, resetLockoutRequiresHardwareAuthToken);
         sensor.init(gestureAvailabilityDispatcher, mLockoutResetDispatcher);
         final int sessionUserId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
                 sensor.getLazySession().get().getUserId();
@@ -329,7 +337,7 @@
     }
 
     private String getTag() {
-        return "FingerprintProvider/" + mHalInstanceName;
+        return TAG + "/" + mHalInstanceName;
     }
 
     boolean hasHalInstance() {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java
index 2cc1879..394f045 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.hardware.biometrics.fingerprint.ISession;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -30,11 +31,11 @@
 
 import java.util.function.Supplier;
 
-public class FingerprintStopUserClient extends StopUserClient<AidlSession> {
+public class FingerprintStopUserClient extends StopUserClient<ISession> {
     private static final String TAG = "FingerprintStopUserClient";
 
     public FingerprintStopUserClient(@NonNull Context context,
-            @NonNull Supplier<AidlSession> lazyDaemon, @Nullable IBinder token, int userId,
+            @NonNull Supplier<ISession> lazyDaemon, @Nullable IBinder token, int userId,
             int sensorId,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull UserStoppedCallback callback) {
@@ -50,7 +51,7 @@
     @Override
     protected void startHalOperation() {
         try {
-            getFreshDaemon().getSession().close();
+            getFreshDaemon().close();
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception", e);
             getCallback().onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index dd887bb..af88c62 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -59,6 +59,7 @@
 import com.android.server.biometrics.sensors.StartUserClient;
 import com.android.server.biometrics.sensors.StopUserClient;
 import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
+import com.android.server.biometrics.sensors.UserSwitchProvider;
 import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
 
@@ -77,15 +78,16 @@
 @SuppressWarnings("deprecation")
 public class Sensor {
 
+    private static final String TAG = "Sensor";
+
     private boolean mTestHalEnabled;
 
-    @NonNull private final String mTag;
     @NonNull private final FingerprintProvider mProvider;
     @NonNull private final Context mContext;
     @NonNull private final IBinder mToken;
     @NonNull private final Handler mHandler;
     @NonNull private final FingerprintSensorPropertiesInternal mSensorProperties;
-    @NonNull private BiometricScheduler mScheduler;
+    @NonNull private BiometricScheduler<IFingerprint, ISession> mScheduler;
     @NonNull private LockoutTracker mLockoutTracker;
     @NonNull private final Map<Integer, Long> mAuthenticatorIds;
     @NonNull private final BiometricContext mBiometricContext;
@@ -93,13 +95,10 @@
     @Nullable AidlSession mCurrentSession;
     @NonNull private Supplier<AidlSession> mLazySession;
 
-    public Sensor(@NonNull String tag, @NonNull FingerprintProvider provider,
+    public Sensor(@NonNull FingerprintProvider provider,
             @NonNull Context context, @NonNull Handler handler,
             @NonNull FingerprintSensorPropertiesInternal sensorProperties,
-            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
             @NonNull BiometricContext biometricContext, AidlSession session) {
-        mTag = tag;
         mProvider = provider;
         mContext = context;
         mToken = new Binder();
@@ -110,41 +109,52 @@
         mCurrentSession = session;
     }
 
-    Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
+    Sensor(@NonNull FingerprintProvider provider, @NonNull Context context,
             @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
-            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
             @NonNull BiometricContext biometricContext) {
-        this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher,
-                gestureAvailabilityDispatcher, biometricContext, null);
+        this(provider, context, handler, sensorProperties,
+                biometricContext, null);
     }
 
-    Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
+    Sensor(@NonNull FingerprintProvider provider, @NonNull Context context,
             @NonNull Handler handler, @NonNull SensorProps sensorProp,
-            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
             @NonNull BiometricContext biometricContext,
             @NonNull List<SensorLocationInternal> workaroundLocation,
             boolean resetLockoutRequiresHardwareAuthToken) {
-        this(tag, provider, context, handler, getFingerprintSensorPropertiesInternal(sensorProp,
+        this(provider, context, handler, getFingerprintSensorPropertiesInternal(sensorProp,
                         workaroundLocation, resetLockoutRequiresHardwareAuthToken),
-                lockoutResetDispatcher, gestureAvailabilityDispatcher, biometricContext, null);
+                biometricContext, null);
     }
 
     /**
      * Initialize biometric scheduler, lockout tracker and session for the sensor.
      */
-    public void init(GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
-            LockoutResetDispatcher lockoutResetDispatcher) {
-        mScheduler = new UserAwareBiometricScheduler(mTag,
+    public void init(@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
+        if (Flags.deHidl()) {
+            setScheduler(getBiometricSchedulerForInit(gestureAvailabilityDispatcher,
+                    lockoutResetDispatcher));
+        } else {
+            setScheduler(getUserAwareBiometricSchedulerForInit(gestureAvailabilityDispatcher,
+                    lockoutResetDispatcher));
+        }
+        mLockoutTracker = new LockoutCache();
+        mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
+    }
+
+    private BiometricScheduler<IFingerprint, ISession> getBiometricSchedulerForInit(
+            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
+        return new BiometricScheduler<>(mHandler,
                 BiometricScheduler.sensorTypeFromFingerprintProperties(mSensorProperties),
                 gestureAvailabilityDispatcher,
                 () -> mCurrentSession != null ? mCurrentSession.getUserId() : UserHandle.USER_NULL,
-                new UserAwareBiometricScheduler.UserSwitchCallback() {
+                new UserSwitchProvider<IFingerprint, ISession>() {
                     @NonNull
                     @Override
-                    public StopUserClient<?> getStopUserClient(int userId) {
-                        return new FingerprintStopUserClient(mContext, mLazySession, mToken,
+                    public StopUserClient<ISession> getStopUserClient(int userId) {
+                        return new FingerprintStopUserClient(mContext,
+                                () -> mLazySession.get().getSession(), mToken,
                                 userId, mSensorProperties.sensorId,
                                 BiometricLogger.ofUnknown(mContext), mBiometricContext,
                                 () -> mCurrentSession = null);
@@ -152,69 +162,100 @@
 
                     @NonNull
                     @Override
-                    public StartUserClient<?, ?> getStartUserClient(int newUserId) {
+                    public StartUserClient<IFingerprint, ISession> getStartUserClient(
+                            int newUserId) {
                         final int sensorId = mSensorProperties.sensorId;
-
-                        final AidlResponseHandler resultController;
-
-                        if (Flags.deHidl()) {
-                            resultController = new AidlResponseHandler(
-                                    mContext, mScheduler, sensorId, newUserId,
-                                    mLockoutTracker, lockoutResetDispatcher,
-                                    mBiometricContext.getAuthSessionCoordinator(), () -> {},
-                                    new AidlResponseHandler.AidlResponseHandlerCallback() {
-                                        @Override
-                                        public void onEnrollSuccess() {
-                                            mProvider.scheduleLoadAuthenticatorIdsForUser(sensorId,
-                                                    newUserId);
-                                            mProvider.scheduleInvalidationRequest(sensorId,
-                                                    newUserId);
-                                        }
-
-                                        @Override
-                                        public void onHardwareUnavailable() {
-                                            Slog.e(mTag,
-                                                    "Fingerprint sensor hardware unavailable.");
-                                            mCurrentSession = null;
-                                        }
-                                    });
-                        } else {
-                            resultController = new AidlResponseHandler(
-                                    mContext, mScheduler, sensorId, newUserId,
-                                    mLockoutTracker, lockoutResetDispatcher,
-                                    mBiometricContext.getAuthSessionCoordinator(), () -> {
-                                Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
-                                mCurrentSession = null;
-                            });
-                        }
-
-                        final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
-                                (userIdStarted, newSession, halInterfaceVersion) -> {
-                                    Slog.d(mTag, "New session created for user: "
-                                            + userIdStarted + " with hal version: "
-                                            + halInterfaceVersion);
-                                    mCurrentSession = new AidlSession(halInterfaceVersion,
-                                            newSession, userIdStarted, resultController);
-                                    if (FingerprintUtils.getInstance(sensorId)
-                                            .isInvalidationInProgress(mContext, userIdStarted)) {
-                                        Slog.w(mTag,
-                                                "Scheduling unfinished invalidation request for "
-                                                        + "sensor: "
-                                                        + sensorId
-                                                        + ", user: " + userIdStarted);
+                        final AidlResponseHandler resultController = new AidlResponseHandler(
+                                mContext, mScheduler, sensorId, newUserId,
+                                mLockoutTracker, lockoutResetDispatcher,
+                                mBiometricContext.getAuthSessionCoordinator(), () -> {},
+                                new AidlResponseHandler.AidlResponseHandlerCallback() {
+                                    @Override
+                                    public void onEnrollSuccess() {
+                                        mProvider.scheduleLoadAuthenticatorIdsForUser(sensorId,
+                                                newUserId);
                                         mProvider.scheduleInvalidationRequest(sensorId,
-                                                userIdStarted);
+                                                newUserId);
                                     }
-                                };
 
-                        return new FingerprintStartUserClient(mContext, mProvider::getHalInstance,
-                                mToken, newUserId, mSensorProperties.sensorId,
-                                BiometricLogger.ofUnknown(mContext), mBiometricContext,
-                                resultController, userStartedCallback);
+                                    @Override
+                                    public void onHardwareUnavailable() {
+                                        Slog.e(TAG,
+                                                "Fingerprint sensor hardware unavailable.");
+                                        mCurrentSession = null;
+                                    }
+                                });
+
+                        return Sensor.this.getStartUserClient(resultController, sensorId,
+                                newUserId);
                     }
                 });
-        mLockoutTracker = new LockoutCache();
-        mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
+    }
+
+    private UserAwareBiometricScheduler<ISession, AidlSession>
+            getUserAwareBiometricSchedulerForInit(
+                    GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+                    LockoutResetDispatcher lockoutResetDispatcher) {
+        return new UserAwareBiometricScheduler<>(TAG,
+                BiometricScheduler.sensorTypeFromFingerprintProperties(mSensorProperties),
+                gestureAvailabilityDispatcher,
+                () -> mCurrentSession != null ? mCurrentSession.getUserId() : UserHandle.USER_NULL,
+                new UserAwareBiometricScheduler.UserSwitchCallback() {
+                    @NonNull
+                    @Override
+                    public StopUserClient<ISession> getStopUserClient(int userId) {
+                        return new FingerprintStopUserClient(mContext,
+                                () -> mLazySession.get().getSession(), mToken,
+                                userId, mSensorProperties.sensorId,
+                                BiometricLogger.ofUnknown(mContext), mBiometricContext,
+                                () -> mCurrentSession = null);
+                    }
+
+                    @NonNull
+                    @Override
+                    public StartUserClient<IFingerprint, ISession> getStartUserClient(
+                            int newUserId) {
+                        final int sensorId = mSensorProperties.sensorId;
+
+                        final AidlResponseHandler resultController = new AidlResponseHandler(
+                                mContext, mScheduler, sensorId, newUserId,
+                                mLockoutTracker, lockoutResetDispatcher,
+                                mBiometricContext.getAuthSessionCoordinator(), () -> {
+                                    Slog.e(TAG, "Fingerprint hardware unavailable.");
+                                    mCurrentSession = null;
+                                });
+
+                        return Sensor.this.getStartUserClient(resultController, sensorId,
+                                newUserId);
+                    }
+                });
+    }
+
+    private FingerprintStartUserClient getStartUserClient(AidlResponseHandler resultController,
+            int sensorId, int newUserId) {
+        final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
+                (userIdStarted, newSession, halInterfaceVersion) -> {
+                    Slog.d(TAG, "New fingerprint session created for user: "
+                            + userIdStarted + " with hal version: "
+                            + halInterfaceVersion);
+                    mCurrentSession = new AidlSession(halInterfaceVersion,
+                            newSession, userIdStarted, resultController);
+                    if (FingerprintUtils.getInstance(sensorId)
+                            .isInvalidationInProgress(mContext, userIdStarted)) {
+                        Slog.w(TAG,
+                                "Scheduling unfinished invalidation request for "
+                                        + "fingerprint sensor: "
+                                        + sensorId
+                                        + ", user: " + userIdStarted);
+                        mProvider.scheduleInvalidationRequest(sensorId,
+                                userIdStarted);
+                    }
+                };
+
+        return new FingerprintStartUserClient(mContext, mProvider::getHalInstance,
+                mToken, newUserId, mSensorProperties.sensorId,
+                BiometricLogger.ofUnknown(mContext), mBiometricContext,
+                resultController, userStartedCallback);
     }
 
     protected static FingerprintSensorPropertiesInternal getFingerprintSensorPropertiesInternal(
@@ -267,7 +308,7 @@
                 biometricStateCallback, mProvider, this);
     }
 
-    @NonNull public BiometricScheduler getScheduler() {
+    @NonNull public BiometricScheduler<IFingerprint, ISession> getScheduler() {
         return mScheduler;
     }
 
@@ -283,17 +324,17 @@
     }
 
     void setTestHalEnabled(boolean enabled) {
-        Slog.w(mTag, "setTestHalEnabled: " + enabled);
+        Slog.w(TAG, "Fingerprint setTestHalEnabled: " + enabled);
         if (enabled != mTestHalEnabled) {
             // The framework should retrieve a new session from the HAL.
             try {
                 if (mCurrentSession != null) {
                     // TODO(181984005): This should be scheduled instead of directly invoked
-                    Slog.d(mTag, "Closing old session");
+                    Slog.d(TAG, "Closing old fingerprint session");
                     mCurrentSession.getSession().close();
                 }
             } catch (RemoteException e) {
-                Slog.e(mTag, "RemoteException", e);
+                Slog.e(TAG, "RemoteException", e);
             }
             mCurrentSession = null;
         }
@@ -335,7 +376,7 @@
     public void onBinderDied() {
         final BaseClientMonitor client = mScheduler.getCurrentClient();
         if (client instanceof ErrorConsumer) {
-            Slog.e(mTag, "Sending ERROR_HW_UNAVAILABLE for client: " + client);
+            Slog.e(TAG, "Sending fingerprint hardware unavailable error for client: " + client);
             final ErrorConsumer errorConsumer = (ErrorConsumer) client;
             errorConsumer.onError(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE,
                     0 /* vendorCode */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index d3cecd0..4accf8f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -119,7 +119,7 @@
     @NonNull private final AuthenticationStateListeners mAuthenticationStateListeners;
     private final ActivityTaskManager mActivityTaskManager;
     @NonNull private final FingerprintSensorPropertiesInternal mSensorProperties;
-    private final BiometricScheduler mScheduler;
+    private final BiometricScheduler<IBiometricsFingerprint, AidlSession> mScheduler;
     private final Handler mHandler;
     private final LockoutResetDispatcher mLockoutResetDispatcher;
     private final LockoutFrameworkImpl mLockoutTracker;
@@ -198,11 +198,11 @@
         private final int mSensorId;
         @NonNull private final Context mContext;
         @NonNull final Handler mHandler;
-        @NonNull final BiometricScheduler mScheduler;
+        @NonNull final BiometricScheduler<IBiometricsFingerprint, AidlSession> mScheduler;
         @Nullable private Callback mCallback;
 
         HalResultController(int sensorId, @NonNull Context context, @NonNull Handler handler,
-                @NonNull BiometricScheduler scheduler) {
+                @NonNull BiometricScheduler<IBiometricsFingerprint, AidlSession> scheduler) {
             mSensorId = sensorId;
             mContext = context;
             mHandler = handler;
@@ -336,7 +336,7 @@
             @NonNull BiometricStateCallback biometricStateCallback,
             @NonNull AuthenticationStateListeners authenticationStateListeners,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
-            @NonNull BiometricScheduler scheduler,
+            @NonNull BiometricScheduler<IBiometricsFingerprint, AidlSession> scheduler,
             @NonNull Handler handler,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull HalResultController controller,
@@ -389,8 +389,8 @@
             @NonNull Handler handler,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
-        final BiometricScheduler scheduler =
-                new BiometricScheduler(TAG,
+        final BiometricScheduler<IBiometricsFingerprint, AidlSession> scheduler =
+                new BiometricScheduler<>(
                         BiometricScheduler.sensorTypeFromFingerprintProperties(sensorProps),
                         gestureAvailabilityDispatcher);
         final HalResultController controller = new HalResultController(sensorProps.sensorId,
@@ -533,8 +533,8 @@
     private void scheduleUpdateActiveUserWithoutHandler(int targetUserId, boolean force) {
         final boolean hasEnrolled =
                 !getEnrolledFingerprints(mSensorProperties.sensorId, targetUserId).isEmpty();
-        final FingerprintUpdateActiveUserClient client =
-                new FingerprintUpdateActiveUserClient(mContext, mLazyDaemon, targetUserId,
+        final FingerprintUpdateActiveUserClientLegacy client =
+                new FingerprintUpdateActiveUserClientLegacy(mContext, mLazyDaemon, targetUserId,
                         mContext.getOpPackageName(), mSensorProperties.sensorId,
                         createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
                                 BiometricsProtoEnums.CLIENT_UNKNOWN,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
index 88dae6f..9232e11 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
@@ -140,9 +140,9 @@
     private static class TestableBiometricScheduler extends BiometricScheduler {
         @NonNull private Fingerprint21UdfpsMock mFingerprint21;
 
-        TestableBiometricScheduler(@NonNull String tag, @NonNull Handler handler,
+        TestableBiometricScheduler(
                 @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
-            super(tag, BiometricScheduler.SENSOR_TYPE_FP_OTHER, gestureAvailabilityDispatcher);
+            super(BiometricScheduler.SENSOR_TYPE_FP_OTHER, gestureAvailabilityDispatcher);
         }
 
         void init(@NonNull Fingerprint21UdfpsMock fingerprint21) {
@@ -258,7 +258,7 @@
 
         final Handler handler = new Handler(Looper.getMainLooper());
         final TestableBiometricScheduler scheduler =
-                new TestableBiometricScheduler(TAG, handler, gestureAvailabilityDispatcher);
+                new TestableBiometricScheduler(gestureAvailabilityDispatcher);
         final MockHalResultController controller =
                 new MockHalResultController(sensorProps.sensorId, context, handler, scheduler);
         return new Fingerprint21UdfpsMock(context, biometricStateCallback,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
index 5c5b992..59e64cd 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
@@ -18,7 +18,7 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.hardware.biometrics.fingerprint.ISession;
 import android.os.Build;
 import android.os.Environment;
 import android.os.RemoteException;
@@ -39,8 +39,8 @@
 /**
  * Sets the HAL's current active user, and updates the framework's authenticatorId cache.
  */
-public class FingerprintUpdateActiveUserClient extends
-        StartUserClient<IBiometricsFingerprint, AidlSession> {
+public class FingerprintUpdateActiveUserClient extends StartUserClient<ISession,
+        AidlSession> {
 
     private static final String TAG = "FingerprintUpdateActiveUserClient";
     private static final String FP_DATA_DIR = "fpdata";
@@ -52,19 +52,7 @@
     private File mDirectory;
 
     FingerprintUpdateActiveUserClient(@NonNull Context context,
-            @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
-            @NonNull String owner, int sensorId,
-            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
-            @NonNull Supplier<Integer> currentUserId,
-            boolean hasEnrolledBiometrics, @NonNull Map<Integer, Long> authenticatorIds,
-            boolean forceUpdateAuthenticatorId) {
-        this(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext, currentUserId,
-                hasEnrolledBiometrics, authenticatorIds, forceUpdateAuthenticatorId,
-                (newUserId, newUser, halInterfaceVersion) -> {});
-    }
-
-    FingerprintUpdateActiveUserClient(@NonNull Context context,
-            @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
+            @NonNull Supplier<ISession> lazyDaemon, int userId,
             @NonNull String owner, int sensorId,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull Supplier<Integer> currentUserId,
@@ -132,9 +120,10 @@
         try {
             final int targetId = getTargetUserId();
             Slog.d(TAG, "Setting active user: " + targetId);
-            getFreshDaemon().setActiveGroup(targetId, mDirectory.getAbsolutePath());
+            HidlToAidlSessionAdapter sessionAdapter = (HidlToAidlSessionAdapter) getFreshDaemon();
+            sessionAdapter.setActiveGroup(targetId, mDirectory.getAbsolutePath());
             mAuthenticatorIds.put(targetId, mHasEnrolledBiometrics
-                    ? getFreshDaemon().getAuthenticatorId() : 0L);
+                    ? sessionAdapter.getAuthenticatorIdForUpdateClient() : 0L);
             mUserStartedCallback.onUserStarted(targetId, null, 0);
             mCallback.onClientFinished(this, true /* success */);
         } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClientLegacy.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClientLegacy.java
new file mode 100644
index 0000000..fc85402
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClientLegacy.java
@@ -0,0 +1,133 @@
+/*
+ * 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.server.biometrics.sensors.fingerprint.hidl;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.os.Build;
+import android.os.Environment;
+import android.os.RemoteException;
+import android.os.SELinux;
+import android.util.Slog;
+
+import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.HalClientMonitor;
+
+import java.io.File;
+import java.util.Map;
+import java.util.function.Supplier;
+
+/**
+ * TODO(b/304604965): Delete this class once Flags.DE_HIDL is ready for release.
+ */
+public class FingerprintUpdateActiveUserClientLegacy extends
+        HalClientMonitor<IBiometricsFingerprint> {
+    private static final String TAG = "FingerprintUpdateActiveUserClient";
+    private static final String FP_DATA_DIR = "fpdata";
+
+    private final Supplier<Integer> mCurrentUserId;
+    private final boolean mForceUpdateAuthenticatorId;
+    private final boolean mHasEnrolledBiometrics;
+    private final Map<Integer, Long> mAuthenticatorIds;
+    private File mDirectory;
+
+    FingerprintUpdateActiveUserClientLegacy(@NonNull Context context,
+            @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
+            @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            @NonNull Supplier<Integer> currentUserId,
+            boolean hasEnrolledBiometrics, @NonNull Map<Integer, Long> authenticatorIds,
+            boolean forceUpdateAuthenticatorId) {
+        super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
+                0 /* cookie */, sensorId, logger, biometricContext);
+        mCurrentUserId = currentUserId;
+        mForceUpdateAuthenticatorId = forceUpdateAuthenticatorId;
+        mHasEnrolledBiometrics = hasEnrolledBiometrics;
+        mAuthenticatorIds = authenticatorIds;
+    }
+
+    @Override
+    public void start(@NonNull ClientMonitorCallback callback) {
+        super.start(callback);
+
+        if (mCurrentUserId.get() == getTargetUserId() && !mForceUpdateAuthenticatorId) {
+            Slog.d(TAG, "Already user: " + mCurrentUserId + ", returning");
+            callback.onClientFinished(this, true /* success */);
+            return;
+        }
+
+        int firstSdkInt = Build.VERSION.DEVICE_INITIAL_SDK_INT;
+        if (firstSdkInt < Build.VERSION_CODES.BASE) {
+            Slog.e(TAG, "First SDK version " + firstSdkInt + " is invalid; must be "
+                    + "at least VERSION_CODES.BASE");
+        }
+        File baseDir;
+        if (firstSdkInt <= Build.VERSION_CODES.O_MR1) {
+            baseDir = Environment.getUserSystemDirectory(getTargetUserId());
+        } else {
+            baseDir = Environment.getDataVendorDeDirectory(getTargetUserId());
+        }
+
+        mDirectory = new File(baseDir, FP_DATA_DIR);
+        if (!mDirectory.exists()) {
+            if (!mDirectory.mkdir()) {
+                Slog.e(TAG, "Cannot make directory: " + mDirectory.getAbsolutePath());
+                callback.onClientFinished(this, false /* success */);
+                return;
+            }
+            // Calling mkdir() from this process will create a directory with our
+            // permissions (inherited from the containing dir). This command fixes
+            // the label.
+            if (!SELinux.restorecon(mDirectory)) {
+                Slog.e(TAG, "Restorecons failed. Directory will have wrong label.");
+                callback.onClientFinished(this, false /* success */);
+                return;
+            }
+        }
+
+        startHalOperation();
+    }
+
+    @Override
+    public void unableToStart() {
+        // Nothing to do here
+    }
+
+    @Override
+    protected void startHalOperation() {
+        try {
+            final int targetId = getTargetUserId();
+            Slog.d(TAG, "Setting active user: " + targetId);
+            getFreshDaemon().setActiveGroup(targetId, mDirectory.getAbsolutePath());
+            mAuthenticatorIds.put(targetId, mHasEnrolledBiometrics
+                    ? getFreshDaemon().getAuthenticatorId() : 0L);
+            mCallback.onClientFinished(this, true /* success */);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to setActiveGroup: " + e);
+            mCallback.onClientFinished(this, false /* success */);
+        }
+    }
+
+    @Override
+    public int getProtoEnum() {
+        return BiometricsProto.CM_UPDATE_ACTIVE_USER;
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
index 90da74c..47fdcdb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.UserInfo;
+import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.biometrics.fingerprint.SensorProps;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.os.Handler;
@@ -39,7 +40,7 @@
 import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.StartUserClient;
 import com.android.server.biometrics.sensors.StopUserClient;
-import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
+import com.android.server.biometrics.sensors.UserSwitchProvider;
 import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
 import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
@@ -71,37 +72,33 @@
                 }
             };
 
-    public HidlToAidlSensorAdapter(@NonNull String tag, @NonNull FingerprintProvider provider,
-            @NonNull Context context, @NonNull Handler handler,
+    public HidlToAidlSensorAdapter(@NonNull FingerprintProvider provider,
+            @NonNull Context context,
+            @NonNull Handler handler,
             @NonNull SensorProps prop,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
             @NonNull BiometricContext biometricContext,
             boolean resetLockoutRequiresHardwareAuthToken,
             @NonNull Runnable internalCleanupRunnable) {
-        this(tag, provider, context, handler, prop, lockoutResetDispatcher,
-                gestureAvailabilityDispatcher, biometricContext,
+        this(provider, context, handler, prop, lockoutResetDispatcher, biometricContext,
                 resetLockoutRequiresHardwareAuthToken, internalCleanupRunnable,
                 new AuthSessionCoordinator(), null /* daemon */,
                 null /* onEnrollSuccessCallback */);
     }
 
     @VisibleForTesting
-    HidlToAidlSensorAdapter(@NonNull String tag, @NonNull FingerprintProvider provider,
+    HidlToAidlSensorAdapter(@NonNull FingerprintProvider provider,
             @NonNull Context context, @NonNull Handler handler,
             @NonNull SensorProps prop,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
             @NonNull BiometricContext biometricContext,
             boolean resetLockoutRequiresHardwareAuthToken,
             @NonNull Runnable internalCleanupRunnable,
             @NonNull AuthSessionCoordinator authSessionCoordinator,
             @Nullable IBiometricsFingerprint daemon,
             @Nullable AidlResponseHandler.AidlResponseHandlerCallback aidlResponseHandlerCallback) {
-        super(tag, provider, context, handler, getFingerprintSensorPropertiesInternal(prop,
+        super(provider, context, handler, getFingerprintSensorPropertiesInternal(prop,
                         new ArrayList<>(), resetLockoutRequiresHardwareAuthToken),
-                lockoutResetDispatcher,
-                gestureAvailabilityDispatcher,
                 biometricContext, null /* session */);
         mLockoutResetDispatcher = lockoutResetDispatcher;
         mInternalCleanupRunnable = internalCleanupRunnable;
@@ -127,7 +124,7 @@
 
     @Override
     public void serviceDied(long cookie) {
-        Slog.d(TAG, "HAL died.");
+        Slog.d(TAG, "Fingerprint HAL died.");
         mSession = null;
         mDaemon = null;
     }
@@ -139,12 +136,12 @@
     }
 
     @Override
-    public void init(GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
-            LockoutResetDispatcher lockoutResetDispatcher) {
+    public void init(@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
         setLazySession(this::getSession);
-        setScheduler(new UserAwareBiometricScheduler(TAG,
+        setScheduler(new BiometricScheduler<ISession, AidlSession>(getHandler(),
                 BiometricScheduler.sensorTypeFromFingerprintProperties(getSensorProperties()),
-                gestureAvailabilityDispatcher, () -> mCurrentUserId, getUserSwitchCallback()));
+                gestureAvailabilityDispatcher, () -> mCurrentUserId, getUserSwitchProvider()));
         mLockoutTracker = new LockoutFrameworkImpl(getContext(),
                 userId -> mLockoutResetDispatcher.notifyLockoutResetCallbacks(
                         getSensorProperties().sensorId), getHandler());
@@ -152,6 +149,7 @@
 
     @Override
     @Nullable
+    @VisibleForTesting
     protected AidlSession getSessionForUser(int userId) {
         if (mSession != null && mSession.getUserId() == userId) {
             return mSession;
@@ -217,21 +215,18 @@
         }
 
         mDaemon.asBinder().linkToDeath(this, 0 /* flags */);
-
-        Slog.d(TAG, "Fingerprint HAL ready");
-
         scheduleLoadAuthenticatorIds();
         mInternalCleanupRunnable.run();
         return mDaemon;
     }
 
-    private UserAwareBiometricScheduler.UserSwitchCallback getUserSwitchCallback() {
-        return new UserAwareBiometricScheduler.UserSwitchCallback() {
+    private UserSwitchProvider<ISession, AidlSession> getUserSwitchProvider() {
+        return new UserSwitchProvider<>() {
             @NonNull
             @Override
-            public StopUserClient<?> getStopUserClient(int userId) {
-                return new StopUserClient<IBiometricsFingerprint>(getContext(),
-                        HidlToAidlSensorAdapter.this::getIBiometricsFingerprint,
+            public StopUserClient<AidlSession> getStopUserClient(int userId) {
+                return new StopUserClient<>(getContext(),
+                        HidlToAidlSensorAdapter.this::getSession,
                         null /* token */, userId, getSensorProperties().sensorId,
                         BiometricLogger.ofUnknown(getContext()), getBiometricContext(),
                         () -> {
@@ -258,7 +253,7 @@
 
             @NonNull
             @Override
-            public StartUserClient<?, ?> getStartUserClient(int newUserId) {
+            public StartUserClient<ISession, AidlSession> getStartUserClient(int newUserId) {
                 return getFingerprintUpdateActiveUserClient(newUserId,
                         false /* forceUpdateAuthenticatorId */);
             }
@@ -268,7 +263,7 @@
     private FingerprintUpdateActiveUserClient getFingerprintUpdateActiveUserClient(int newUserId,
             boolean forceUpdateAuthenticatorIds) {
         return new FingerprintUpdateActiveUserClient(getContext(),
-                this::getIBiometricsFingerprint, newUserId, TAG,
+                () -> getSession().getSession(), newUserId, TAG,
                 getSensorProperties().sensorId, BiometricLogger.ofUnknown(getContext()),
                 getBiometricContext(), () -> mCurrentUserId,
                 !FingerprintUtils.getInstance(getSensorProperties().sensorId)
@@ -290,7 +285,7 @@
     }
 
     @VisibleForTesting void handleUserChanged(int newUserId) {
-        Slog.d(TAG, "User changed. Current user is " + newUserId);
+        Slog.d(TAG, "User changed. Current user for fingerprint sensor is " + newUserId);
         mSession = null;
         mCurrentUserId = newUserId;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapter.java
index 2fc00e1..b469752 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapter.java
@@ -209,6 +209,14 @@
         return null;
     }
 
+    public long getAuthenticatorIdForUpdateClient() throws RemoteException {
+        return mSession.get().getAuthenticatorId();
+    }
+
+    public void setActiveGroup(int userId, String absolutePath) throws RemoteException {
+        mSession.get().setActiveGroup(userId, absolutePath);
+    }
+
     private void setCallback(AidlResponseHandler aidlResponseHandler) {
         mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler);
         try {
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index bd22e1d..4c4cf608 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -16,6 +16,7 @@
 
 package com.android.server.display;
 
+import static com.android.server.display.BrightnessMappingStrategy.INVALID_NITS;
 import static com.android.server.display.utils.DeviceConfigParsingUtils.ambientBrightnessThresholdsIntToFloat;
 import static com.android.server.display.utils.DeviceConfigParsingUtils.displayBrightnessThresholdsIntToFloat;
 
@@ -567,7 +568,8 @@
 
     public static final int DEFAULT_LOW_REFRESH_RATE = 60;
 
-    private static final float BRIGHTNESS_DEFAULT = 0.5f;
+    @VisibleForTesting
+    static final float BRIGHTNESS_DEFAULT = 0.5f;
     private static final String ETC_DIR = "etc";
     private static final String DISPLAY_CONFIG_DIR = "displayconfig";
     private static final String CONFIG_FILE_FORMAT = "display_%s.xml";
@@ -597,8 +599,6 @@
     // so -2 is used instead
     private static final float INVALID_BRIGHTNESS_IN_CONFIG = -2f;
 
-    static final float NITS_INVALID = -1;
-
     // Length of the ambient light horizon used to calculate the long term estimate of ambient
     // light.
     private static final int AMBIENT_LIGHT_LONG_HORIZON_MILLIS = 10000;
@@ -1031,11 +1031,12 @@
     /**
      * Calculates the nits value for the specified backlight value if a mapping exists.
      *
-     * @return The mapped nits or {@link #NITS_INVALID} if no mapping exits.
+     * @return The mapped nits or {@link BrightnessMappingStrategy.INVALID_NITS} if no mapping
+     * exits.
      */
     public float getNitsFromBacklight(float backlight) {
         if (mBacklightToNitsSpline == null) {
-            return NITS_INVALID;
+            return INVALID_NITS;
         }
         backlight = Math.max(backlight, mBacklightMinimum);
         return mBacklightToNitsSpline.interpolate(backlight);
@@ -1061,7 +1062,7 @@
 
         float backlight = getBacklightFromBrightness(brightness);
         float nits = getNitsFromBacklight(backlight);
-        if (nits == NITS_INVALID) {
+        if (nits == INVALID_NITS) {
             return PowerManager.BRIGHTNESS_INVALID;
         }
 
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 25576ce..3a63330 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -19,6 +19,8 @@
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.Display.Mode.INVALID_MODE_ID;
 
+import static com.android.server.display.BrightnessMappingStrategy.INVALID_NITS;
+
 import android.annotation.Nullable;
 import android.app.ActivityThread;
 import android.content.Context;
@@ -956,8 +958,7 @@
 
                     void handleHdrSdrNitsChanged(float displayNits, float sdrNits) {
                         final float newHdrSdrRatio;
-                        if (displayNits != DisplayDeviceConfig.NITS_INVALID
-                                && sdrNits != DisplayDeviceConfig.NITS_INVALID) {
+                        if (displayNits != INVALID_NITS && sdrNits != INVALID_NITS) {
                             // Ensure the ratio stays >= 1.0f as values below that are nonsensical
                             newHdrSdrRatio = Math.max(1.f, displayNits / sdrNits);
                         } else {
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 0c2eee5..ad09082 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -2118,11 +2118,10 @@
             Slogf.d(TAG, "CE storage for user %d is already unlocked", userId);
             return;
         }
-        final UserInfo userInfo = mUserManager.getUserInfo(userId);
         final String userType = isUserSecure(userId) ? "secured" : "unsecured";
         final byte[] secret = sp.deriveFileBasedEncryptionKey();
         try {
-            mStorageManager.unlockCeStorage(userId, userInfo.serialNumber, secret);
+            mStorageManager.unlockCeStorage(userId, secret);
             Slogf.i(TAG, "Unlocked CE storage for %s user %d", userType, userId);
         } catch (RemoteException e) {
             Slogf.wtf(TAG, e, "Failed to unlock CE storage for %s user %d", userType, userId);
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index a6f71c2..85c4ffe 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -22,6 +22,7 @@
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
 import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
+import static android.media.audio.Flags.focusExclusiveWithRecording;
 import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS;
 import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
 import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS;
@@ -588,30 +589,41 @@
     }
 
     private boolean playSound(final NotificationRecord record, Uri soundUri) {
+        final boolean shouldPlay;
+        if (focusExclusiveWithRecording()) {
+            // flagged path
+            shouldPlay = mAudioManager.shouldNotificationSoundPlay(record.getAudioAttributes());
+        } else {
+            // legacy path
+            // play notifications if there is no user of exclusive audio focus
+            // and the stream volume is not 0 (non-zero volume implies not silenced by SILENT or
+            //   VIBRATE ringer mode)
+            shouldPlay = !mAudioManager.isAudioFocusExclusive()
+                    && (mAudioManager.getStreamVolume(
+                    AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) != 0);
+        }
+        if (!shouldPlay) {
+            if (DEBUG) Slog.v(TAG, "Not playing sound " + soundUri + " due to focus/volume");
+            return false;
+        }
+
         boolean looping = (record.getNotification().flags & FLAG_INSISTENT) != 0;
-        // play notifications if there is no user of exclusive audio focus
-        // and the stream volume is not 0 (non-zero volume implies not silenced by SILENT or
-        //   VIBRATE ringer mode)
-        if (!mAudioManager.isAudioFocusExclusive()
-                && (mAudioManager.getStreamVolume(
-                AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) != 0)) {
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
-                if (player != null) {
-                    if (DEBUG) {
-                        Slog.v(TAG, "Playing sound " + soundUri + " with attributes "
-                                + record.getAudioAttributes());
-                    }
-                    player.playAsync(soundUri, record.getSbn().getUser(), looping,
-                            record.getAudioAttributes(), getSoundVolume(record));
-                    return true;
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
+            if (player != null) {
+                if (DEBUG) {
+                    Slog.v(TAG, "Playing sound " + soundUri + " with attributes "
+                            + record.getAudioAttributes());
                 }
-            } catch (RemoteException e) {
-                Log.e(TAG, "Failed playSound: " + e);
-            } finally {
-                Binder.restoreCallingIdentity(identity);
+                player.playAsync(soundUri, record.getSbn().getUser(), looping,
+                        record.getAudioAttributes(), getSoundVolume(record));
+                return true;
             }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed playSound: " + e);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
         }
         return false;
     }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 9ed3559..7aa7b7e 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -83,6 +83,7 @@
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.media.audio.Flags.focusExclusiveWithRecording;
 import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
 import static android.os.Flags.allowPrivateProfile;
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
@@ -9104,27 +9105,40 @@
     }
 
     private boolean playSound(final NotificationRecord record, Uri soundUri) {
+        final boolean shouldPlay;
+        if (focusExclusiveWithRecording()) {
+            // flagged path
+            shouldPlay = mAudioManager.shouldNotificationSoundPlay(record.getAudioAttributes());
+        } else {
+            // legacy path
+            // play notifications if there is no user of exclusive audio focus
+            // and the stream volume is not 0 (non-zero volume implies not silenced by SILENT or
+            //   VIBRATE ringer mode)
+            shouldPlay = !mAudioManager.isAudioFocusExclusive()
+                    && (mAudioManager.getStreamVolume(
+                        AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) != 0);
+        }
+        if (!shouldPlay) {
+            if (DBG) Slog.v(TAG, "Not playing sound " + soundUri + " due to focus/volume");
+            return false;
+        }
+
         boolean looping = (record.getNotification().flags & FLAG_INSISTENT) != 0;
-        // play notifications if there is no user of exclusive audio focus
-        // and the stream volume is not 0 (non-zero volume implies not silenced by SILENT or
-        //   VIBRATE ringer mode)
-        if (!mAudioManager.isAudioFocusExclusive()
-                && (mAudioManager.getStreamVolume(
-                        AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) != 0)) {
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
-                if (player != null) {
-                    if (DBG) Slog.v(TAG, "Playing sound " + soundUri
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
+            if (player != null) {
+                if (DBG) {
+                    Slog.v(TAG, "Playing sound " + soundUri
                             + " with attributes " + record.getAudioAttributes());
-                    player.playAsync(soundUri, record.getSbn().getUser(), looping,
-                            record.getAudioAttributes(), 1.0f);
-                    return true;
                 }
-            } catch (RemoteException e) {
-            } finally {
-                Binder.restoreCallingIdentity(identity);
+                player.playAsync(soundUri, record.getSbn().getUser(), looping,
+                        record.getAudioAttributes(), 1.0f);
+                return true;
             }
+        } catch (RemoteException e) {
+        } finally {
+            Binder.restoreCallingIdentity(identity);
         }
         return false;
     }
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 64d3a20..1786ac5 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -25,6 +25,7 @@
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE;
 
 import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Flags;
 import android.app.KeyguardManager;
@@ -167,7 +168,7 @@
     private boolean mPreChannelsNotification = true;
     private Uri mSound;
     private VibrationEffect mVibration;
-    private AudioAttributes mAttributes;
+    private @NonNull AudioAttributes mAttributes;
     private NotificationChannel mChannel;
     private ArrayList<String> mPeopleOverride;
     private ArrayList<SnoozeCriterion> mSnoozeCriteria;
@@ -334,7 +335,7 @@
         return vibration;
     }
 
-    private AudioAttributes calculateAttributes() {
+    private @NonNull AudioAttributes calculateAttributes() {
         final Notification n = getSbn().getNotification();
         AudioAttributes attributes = getChannel().getAudioAttributes();
         if (attributes == null) {
@@ -1003,7 +1004,7 @@
     }
 
     public boolean isAudioAttributesUsage(int usage) {
-        return mAttributes != null && mAttributes.getUsage() == usage;
+        return mAttributes.getUsage() == usage;
     }
 
     /**
@@ -1172,7 +1173,7 @@
         return mVibration;
     }
 
-    public AudioAttributes getAudioAttributes() {
+    public @NonNull AudioAttributes getAudioAttributes() {
         return mAttributes;
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 376b061..a4af5e7 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -27,6 +27,7 @@
 import static android.content.pm.PackageInstaller.EXTRA_UNARCHIVE_STATUS;
 import static android.content.pm.PackageInstaller.STATUS_PENDING_USER_ACTION;
 import static android.content.pm.PackageInstaller.UNARCHIVAL_OK;
+import static android.content.pm.PackageInstaller.UNARCHIVAL_STATUS_UNSET;
 import static android.content.pm.PackageManager.DELETE_ARCHIVE;
 import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
 import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT;
@@ -100,6 +101,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 
 /**
@@ -210,7 +212,6 @@
                                 return;
                             }
 
-                            // TODO(b/278553670) Add special strings for the delete dialog
                             mPm.mInstallerService.uninstall(
                                     new VersionedPackage(packageName,
                                             PackageManager.VERSION_CODE_HIGHEST),
@@ -264,7 +265,7 @@
         try {
             // TODO(b/311709794) Make showUnarchivalConfirmation dependent on the compat options.
             requestUnarchive(packageName, callerPackageName,
-                    getOrCreateUnarchiveIntentSender(userId, packageName),
+                    getOrCreateLauncherListener(userId, packageName),
                     UserHandle.of(userId),
                     false /* showUnarchivalConfirmation= */);
         } catch (Throwable t) {
@@ -329,7 +330,7 @@
         return true;
     }
 
-    private IntentSender getOrCreateUnarchiveIntentSender(int userId, String packageName) {
+    private IntentSender getOrCreateLauncherListener(int userId, String packageName) {
         Pair<Integer, String> key = Pair.create(userId, packageName);
         synchronized (mLauncherIntentSenders) {
             IntentSender intentSender = mLauncherIntentSenders.get(key);
@@ -515,7 +516,6 @@
     /**
      * Returns true if the app is archivable.
      */
-    // TODO(b/299299569) Exclude system apps
     public boolean isAppArchivable(@NonNull String packageName, @NonNull UserHandle user) {
         Objects.requireNonNull(packageName);
         Objects.requireNonNull(user);
@@ -685,15 +685,14 @@
                 PackageInstaller.SessionParams.MODE_FULL_INSTALL);
         sessionParams.setAppPackageName(packageName);
         sessionParams.installFlags = INSTALL_UNARCHIVE_DRAFT;
-        sessionParams.unarchiveIntentSender = statusReceiver;
 
         int installerUid = mPm.snapshotComputer().getPackageUid(installerPackage, 0, userId);
         // Handles case of repeated unarchival calls for the same package.
-        // TODO(b/316881759) Allow attaching multiple intentSenders to one session.
         int existingSessionId = mPm.mInstallerService.getExistingDraftSessionId(installerUid,
                 sessionParams,
                 userId);
         if (existingSessionId != PackageInstaller.SessionInfo.INVALID_ID) {
+            attachListenerToSession(statusReceiver, existingSessionId, userId);
             return existingSessionId;
         }
 
@@ -702,12 +701,34 @@
                 installerPackage, mContext.getAttributionTag(),
                 installerUid,
                 userId);
+        attachListenerToSession(statusReceiver, sessionId, userId);
+
         // TODO(b/297358628) Also cleanup sessions upon device restart.
         mPm.mHandler.postDelayed(() -> mPm.mInstallerService.cleanupDraftIfUnclaimed(sessionId),
                 getUnarchiveForegroundTimeout());
         return sessionId;
     }
 
+    private void attachListenerToSession(IntentSender statusReceiver, int existingSessionId,
+            int userId) {
+        PackageInstallerSession session = mPm.mInstallerService.getSession(existingSessionId);
+        int status = session.getUnarchivalStatus();
+        // Here we handle a race condition that might happen when an installer reports UNARCHIVAL_OK
+        // but hasn't created a session yet. Without this the listener would never receive a success
+        // response.
+        if (status == UNARCHIVAL_OK) {
+            notifyUnarchivalListener(UNARCHIVAL_OK, session.getInstallerPackageName(),
+                    session.params.appPackageName, /* requiredStorageBytes= */ 0,
+                    /* userActionIntent= */ null, Set.of(statusReceiver), userId);
+            return;
+        } else if (status != UNARCHIVAL_STATUS_UNSET) {
+            throw new IllegalStateException(TextUtils.formatSimple("Session %s has unarchive status"
+                    + "%s but is still active.", session.sessionId, status));
+        }
+
+        session.registerUnarchivalListener(statusReceiver);
+    }
+
     /**
      * Returns the icon of an archived app. This is the icon of the main activity of the app.
      *
@@ -883,13 +904,7 @@
 
     void notifyUnarchivalListener(int status, String installerPackageName, String appPackageName,
             long requiredStorageBytes, @Nullable PendingIntent userActionIntent,
-            @Nullable IntentSender unarchiveIntentSender, int userId) {
-        if (unarchiveIntentSender == null) {
-            // Maybe this can happen if the installer calls reportUnarchivalStatus twice in quick
-            // succession.
-            return;
-        }
-
+            Set<IntentSender> unarchiveIntentSenders, int userId) {
         final Intent broadcastIntent = new Intent();
         broadcastIntent.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, appPackageName);
         broadcastIntent.putExtra(EXTRA_UNARCHIVE_STATUS, status);
@@ -909,15 +924,16 @@
         final BroadcastOptions options = BroadcastOptions.makeBasic();
         options.setPendingIntentBackgroundActivityStartMode(
                 MODE_BACKGROUND_ACTIVITY_START_DENIED);
-        try {
-            unarchiveIntentSender.sendIntent(mContext, 0, broadcastIntent, /* onFinished= */ null,
-                    /* handler= */ null, /* requiredPermission= */ null,
-                    options.toBundle());
-        } catch (IntentSender.SendIntentException e) {
-            Slog.e(TAG, TextUtils.formatSimple("Failed to send unarchive intent"), e);
-        } finally {
-            synchronized (mLauncherIntentSenders) {
-                mLauncherIntentSenders.remove(Pair.create(userId, appPackageName));
+        for (IntentSender intentSender : unarchiveIntentSenders) {
+            try {
+                intentSender.sendIntent(mContext, 0, broadcastIntent, /* onFinished= */ null,
+                        /* handler= */ null, /* requiredPermission= */ null, options.toBundle());
+            } catch (IntentSender.SendIntentException e) {
+                Slog.e(TAG, TextUtils.formatSimple("Failed to send unarchive intent"), e);
+            } finally {
+                synchronized (mLauncherIntentSenders) {
+                    mLauncherIntentSenders.remove(Pair.create(userId, appPackageName));
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 0a23dfb..a9118d4 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -1759,26 +1759,8 @@
                         binderUid, unarchiveId));
             }
 
-            IntentSender unarchiveIntentSender = session.params.unarchiveIntentSender;
-            if (unarchiveIntentSender == null) {
-                throw new IllegalStateException(
-                        TextUtils.formatSimple(
-                                "Unarchival status for ID %s has already been set or a "
-                                        + "session has been created for it already by the "
-                                        + "caller.",
-                                unarchiveId));
-            }
-
-            // Execute expensive calls outside the sync block.
-            mPm.mHandler.post(
-                    () -> mPackageArchiver.notifyUnarchivalListener(status,
-                            session.getInstallerPackageName(),
-                            session.params.appPackageName, requiredStorageBytes, userActionIntent,
-                            unarchiveIntentSender, userId));
-            session.params.unarchiveIntentSender = null;
-            if (status != UNARCHIVAL_OK) {
-                Binder.withCleanCallingIdentity(session::abandon);
-            }
+            session.reportUnarchivalStatus(unarchiveId, status, requiredStorageBytes,
+                    userActionIntent);
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 4adb60c..117d03f 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -22,6 +22,8 @@
 import static android.content.pm.DataLoaderType.INCREMENTAL;
 import static android.content.pm.DataLoaderType.STREAMING;
 import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
+import static android.content.pm.PackageInstaller.UNARCHIVAL_OK;
+import static android.content.pm.PackageInstaller.UNARCHIVAL_STATUS_UNSET;
 import static android.content.pm.PackageItemInfo.MAX_SAFE_LABEL_LENGTH;
 import static android.content.pm.PackageManager.INSTALL_FAILED_ABORTED;
 import static android.content.pm.PackageManager.INSTALL_FAILED_BAD_SIGNATURE;
@@ -65,6 +67,7 @@
 import android.app.BroadcastOptions;
 import android.app.Notification;
 import android.app.NotificationManager;
+import android.app.PendingIntent;
 import android.app.admin.DevicePolicyEventLogger;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManagerInternal;
@@ -97,6 +100,7 @@
 import android.content.pm.PackageInstaller.PreapprovalDetails;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.PackageInstaller.SessionParams;
+import android.content.pm.PackageInstaller.UnarchivalStatus;
 import android.content.pm.PackageInstaller.UserActionReason;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.PackageInfoFlags;
@@ -771,6 +775,10 @@
     private final List<String> mResolvedInstructionSets = new ArrayList<>();
     @GuardedBy("mLock")
     private final List<String> mResolvedNativeLibPaths = new ArrayList<>();
+
+    @GuardedBy("mLock")
+    private final Set<IntentSender> mUnarchivalListeners = new ArraySet<>();
+
     @GuardedBy("mLock")
     private File mInheritedFilesBase;
     @GuardedBy("mLock")
@@ -796,6 +804,9 @@
     @GuardedBy("mLock")
     private int mValidatedTargetSdk = INVALID_TARGET_SDK_VERSION;
 
+    @UnarchivalStatus
+    private int mUnarchivalStatus = UNARCHIVAL_STATUS_UNSET;
+
     private static final FileFilter sAddedApkFilter = new FileFilter() {
         @Override
         public boolean accept(File file) {
@@ -5088,6 +5099,44 @@
         }
     }
 
+    void registerUnarchivalListener(IntentSender intentSender) {
+        synchronized (mLock) {
+            this.mUnarchivalListeners.add(intentSender);
+        }
+    }
+
+    Set<IntentSender> getUnarchivalListeners() {
+        synchronized (mLock) {
+            return new ArraySet<>(mUnarchivalListeners);
+        }
+    }
+
+    void reportUnarchivalStatus(@UnarchivalStatus int status, int unarchiveId,
+            long requiredStorageBytes, PendingIntent userActionIntent) {
+        if (getUnarchivalStatus() != UNARCHIVAL_STATUS_UNSET) {
+            throw new IllegalStateException(
+                    TextUtils.formatSimple(
+                            "Unarchival status for ID %s has already been set or a session has "
+                                    + "been created for it already by the caller.",
+                            unarchiveId));
+        }
+        mUnarchivalStatus = status;
+
+        // Execute expensive calls outside the sync block.
+        mPm.mHandler.post(
+                () -> mPm.mInstallerService.mPackageArchiver.notifyUnarchivalListener(status,
+                        getInstallerPackageName(), params.appPackageName, requiredStorageBytes,
+                        userActionIntent, getUnarchivalListeners(), userId));
+        if (status != UNARCHIVAL_OK) {
+            Binder.withCleanCallingIdentity(this::abandon);
+        }
+    }
+
+    @UnarchivalStatus
+    int getUnarchivalStatus() {
+        return this.mUnarchivalStatus;
+    }
+
     /**
      * Free up storage used by this session and its children.
      * Must not be called on a child session.
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index 7d87d1b..cef3244 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -193,7 +193,7 @@
             }
 
             try {
-                sm.prepareUserStorage(volumeUuid, user.id, user.serialNumber, flags);
+                sm.prepareUserStorage(volumeUuid, user.id, flags);
                 synchronized (mPm.mInstallLock) {
                     appDataHelper.reconcileAppsDataLI(volumeUuid, user.id, flags,
                             true /* migrateAppData */);
diff --git a/services/core/java/com/android/server/pm/UserDataPreparer.java b/services/core/java/com/android/server/pm/UserDataPreparer.java
index 8adb566..4c42c2d 100644
--- a/services/core/java/com/android/server/pm/UserDataPreparer.java
+++ b/services/core/java/com/android/server/pm/UserDataPreparer.java
@@ -92,7 +92,7 @@
                 volumeUuid, userId, flags, isNewUser);
         try {
             // Prepare CE and/or DE storage.
-            storage.prepareUserStorage(volumeUuid, userId, userSerial, flags);
+            storage.prepareUserStorage(volumeUuid, userId, flags);
 
             // Ensure that the data directories of a removed user with the same ID are not being
             // reused.  New users must get fresh data directories, to avoid leaking data.
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 75b4531..49af4fe 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -5136,7 +5136,7 @@
 
             t.traceBegin("createUserStorageKeys");
             final StorageManager storage = mContext.getSystemService(StorageManager.class);
-            storage.createUserStorageKeys(userId, userInfo.serialNumber, userInfo.isEphemeral());
+            storage.createUserStorageKeys(userId, userInfo.isEphemeral());
             t.traceEnd();
 
             // Only prepare DE storage here.  CE storage will be prepared later, when the user is
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 5d716fc..e3eb5b5 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -1891,7 +1891,8 @@
         };
     }
 
-    private final PackageMonitor mPackageMonitor = new PackageMonitor() {
+    @VisibleForTesting
+    final PackageMonitor mPackageMonitor = new PackageMonitor() {
         @Override
         public void onSomePackagesChanged() {
             refreshAgentList(UserHandle.USER_ALL);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 56bef33..e1bf8f8 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -2596,6 +2596,10 @@
                 change.setBackgroundColor(ColorUtils.setAlphaComponent(backgroundColor, 255));
             }
 
+            if (activityRecord != null) {
+                change.setActivityComponent(activityRecord.mActivityComponent);
+            }
+
             change.setRotation(info.mRotation, endRotation);
             if (info.mSnapshot != null) {
                 change.setSnapshot(info.mSnapshot, info.mSnapshotLuma);
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index dfb5a57..fb0729f 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -517,6 +517,8 @@
                                     .map(CredentialOption::getType)
                                     .collect(Collectors.toList()));
 
+            finalizeAndEmitInitialPhaseMetric(session);
+
             if (providerSessions.isEmpty()) {
                 try {
                     callback.onError(
@@ -776,6 +778,13 @@
             providerSessions.forEach(ProviderSession::invokeSession);
         }
 
+        private void finalizeAndEmitInitialPhaseMetric(GetCandidateRequestSession session) {
+            var initMetric = session.mRequestSessionMetric.getInitialPhaseMetric();
+            initMetric.setAutofillSessionId(session.getAutofillSessionId());
+            initMetric.setAutofillRequestId(session.getAutofillRequestId());
+            finalizeAndEmitInitialPhaseMetric((RequestSession) session);
+        }
+
         private void finalizeAndEmitInitialPhaseMetric(RequestSession session) {
             try {
                 var initMetric = session.mRequestSessionMetric.getInitialPhaseMetric();
diff --git a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
index d165171..0f914c3 100644
--- a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
@@ -49,7 +49,12 @@
         implements ProviderSession.ProviderInternalCallback<GetCredentialResponse> {
     private static final String TAG = "GetCandidateRequestSession";
 
+    private static final String SESSION_ID_KEY = "autofill_session_id";
+    private static final String REQUEST_ID_KEY = "autofill_request_id";
+
     private final IAutoFillManagerClient mAutoFillCallback;
+    private final int mAutofillSessionId;
+    private final int mAutofillRequestId;
 
     public GetCandidateRequestSession(
             Context context, SessionLifetime sessionCallback,
@@ -62,6 +67,8 @@
                 RequestInfo.TYPE_GET, callingAppInfo, enabledProviders,
                 cancellationSignal, 0L);
         mAutoFillCallback = autoFillCallback;
+        mAutofillSessionId = request.getData().getInt(SESSION_ID_KEY, -1);
+        mAutofillRequestId = request.getData().getInt(REQUEST_ID_KEY, -1);
     }
 
     /**
@@ -177,4 +184,19 @@
         Slog.d(TAG, "onFinalResponseReceived");
         respondToClientWithResponseAndFinish(new GetCandidateCredentialsResponse(response));
     }
+
+    /**
+     * Returns autofill session id. Returns -1 if unavailable.
+     */
+    public int getAutofillSessionId() {
+        return mAutofillSessionId;
+    }
+
+    /**
+     * Returns autofill request id. Returns -1 if unavailable.
+     */
+    public int getAutofillRequestId() {
+        return mAutofillRequestId;
+
+    }
 }
diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index b36de0b..23aa374 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -426,7 +426,11 @@
                     /* per_classtype_counts */
                     initialPhaseMetric.getUniqueRequestCounts(),
                     /* origin_specified */
-                    initialPhaseMetric.isOriginSpecified()
+                    initialPhaseMetric.isOriginSpecified(),
+                    /* autofill_session_id */
+                    initialPhaseMetric.getAutofillSessionId(),
+                    /* autofill_request_id */
+                    initialPhaseMetric.getAutofillRequestId()
             );
         } catch (Exception e) {
             Slog.w(TAG, "Unexpected error during initial metric emit: " + e);
diff --git a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
index 8e965e3..8a4e86c 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
@@ -49,6 +49,12 @@
     // Stores the deduped request information, particularly {"req":5}
     private Map<String, Integer> mRequestCounts = new LinkedHashMap<>();
 
+    // The session id of autofill if the request is from autofill, defaults to -1
+    private int mAutofillSessionId = -1;
+
+    // The request id of autofill if the request is from autofill, defaults to -1
+    private int mAutofillRequestId = -1;
+
 
     public InitialPhaseMetric(int sessionIdTrackOne) {
         mSessionIdCaller = sessionIdTrackOne;
@@ -126,6 +132,24 @@
         return mOriginSpecified;
     }
 
+    /* ------ Autofill Integration ------ */
+
+    public void setAutofillSessionId(int autofillSessionId) {
+        mAutofillSessionId = autofillSessionId;
+    }
+
+    public int getAutofillSessionId() {
+        return mAutofillSessionId;
+    }
+
+    public void setAutofillRequestId(int autofillRequestId) {
+        mAutofillRequestId = autofillRequestId;
+    }
+
+    public int getAutofillRequestId() {
+        return mAutofillRequestId;
+    }
+
     /* ------ Unique Request Counts Map Information ------ */
 
     public void setRequestCounts(Map<String, Integer> requestCounts) {
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
index e5be4d9..9e11fa2 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
@@ -50,7 +50,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 
-// atest PackageManagerServiceTest:com.android.server.pm.UserDataPreparerTest
+// atest PackageManagerServiceServerTests:com.android.server.pm.UserDataPreparerTest
 @RunWith(AndroidJUnit4.class)
 @Presubmit
 @SmallTest
@@ -99,7 +99,7 @@
         systemDeDir.mkdirs();
         mUserDataPreparer.prepareUserData(TEST_USER, StorageManager.FLAG_STORAGE_DE);
         verify(mStorageManagerMock).prepareUserStorage(isNull(String.class), eq(TEST_USER_ID),
-                eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_DE));
+                eq(StorageManager.FLAG_STORAGE_DE));
         verify(mInstaller).createUserData(isNull(String.class), eq(TEST_USER_ID),
                 eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_DE));
         int serialNumber = UserDataPreparer.getSerialNumber(userDeDir);
@@ -116,7 +116,7 @@
         systemCeDir.mkdirs();
         mUserDataPreparer.prepareUserData(TEST_USER, StorageManager.FLAG_STORAGE_CE);
         verify(mStorageManagerMock).prepareUserStorage(isNull(String.class), eq(TEST_USER_ID),
-                eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_CE));
+                eq(StorageManager.FLAG_STORAGE_CE));
         verify(mInstaller).createUserData(isNull(String.class), eq(TEST_USER_ID),
                 eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_CE));
         int serialNumber = UserDataPreparer.getSerialNumber(userCeDir);
@@ -129,7 +129,7 @@
     public void testPrepareUserData_forNewUser_destroysOnFailure() throws Exception {
         TEST_USER.lastLoggedInTime = 0;
         doThrow(new IllegalStateException("expected exception for test")).when(mStorageManagerMock)
-                .prepareUserStorage(isNull(String.class), eq(TEST_USER_ID), eq(TEST_USER_SERIAL),
+                .prepareUserStorage(isNull(String.class), eq(TEST_USER_ID),
                         eq(StorageManager.FLAG_STORAGE_CE));
         mUserDataPreparer.prepareUserData(TEST_USER, StorageManager.FLAG_STORAGE_CE);
         verify(mStorageManagerMock).destroyUserStorage(isNull(String.class), eq(TEST_USER_ID),
@@ -140,7 +140,7 @@
     public void testPrepareUserData_forExistingUser_doesNotDestroyOnFailure() throws Exception {
         TEST_USER.lastLoggedInTime = System.currentTimeMillis();
         doThrow(new IllegalStateException("expected exception for test")).when(mStorageManagerMock)
-                .prepareUserStorage(isNull(String.class), eq(TEST_USER_ID), eq(TEST_USER_SERIAL),
+                .prepareUserStorage(isNull(String.class), eq(TEST_USER_ID),
                         eq(StorageManager.FLAG_STORAGE_CE));
         mUserDataPreparer.prepareUserData(TEST_USER, StorageManager.FLAG_STORAGE_CE);
         verify(mStorageManagerMock, never()).destroyUserStorage(isNull(String.class),
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index e370f55..c67e7c5 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -41,6 +41,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
 import android.os.Temperature;
 import android.provider.Settings;
 import android.util.SparseArray;
@@ -109,6 +110,43 @@
     }
 
     @Test
+    public void testDefaultValues() {
+        when(mResources.getString(com.android.internal.R.string.config_displayLightSensorType))
+                .thenReturn("test_light_sensor");
+        when(mResources.getBoolean(R.bool.config_automatic_brightness_available)).thenReturn(true);
+
+        mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, /* useConfigXml= */ false,
+                mFlags);
+
+        assertEquals(DisplayDeviceConfig.BRIGHTNESS_DEFAULT,
+                mDisplayDeviceConfig.getBrightnessDefault(), ZERO_DELTA);
+        assertEquals(PowerManager.BRIGHTNESS_MAX,
+                mDisplayDeviceConfig.getBrightnessRampFastDecrease(), ZERO_DELTA);
+        assertEquals(PowerManager.BRIGHTNESS_MAX,
+                mDisplayDeviceConfig.getBrightnessRampFastIncrease(), ZERO_DELTA);
+        assertEquals(PowerManager.BRIGHTNESS_MAX,
+                mDisplayDeviceConfig.getBrightnessRampSlowDecrease(), ZERO_DELTA);
+        assertEquals(PowerManager.BRIGHTNESS_MAX,
+                mDisplayDeviceConfig.getBrightnessRampSlowIncrease(), ZERO_DELTA);
+        assertEquals(PowerManager.BRIGHTNESS_MAX,
+                mDisplayDeviceConfig.getBrightnessRampSlowDecreaseIdle(), ZERO_DELTA);
+        assertEquals(PowerManager.BRIGHTNESS_MAX,
+                mDisplayDeviceConfig.getBrightnessRampSlowIncreaseIdle(), ZERO_DELTA);
+        assertEquals(0, mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis());
+        assertEquals(0, mDisplayDeviceConfig.getBrightnessRampIncreaseMaxMillis());
+        assertEquals(0, mDisplayDeviceConfig.getBrightnessRampDecreaseMaxIdleMillis());
+        assertEquals(0, mDisplayDeviceConfig.getBrightnessRampIncreaseMaxIdleMillis());
+        assertNull(mDisplayDeviceConfig.getNits());
+        assertNull(mDisplayDeviceConfig.getBacklight());
+        assertEquals(0.3f, mDisplayDeviceConfig.getBacklightFromBrightness(0.3f), ZERO_DELTA);
+        assertEquals("test_light_sensor", mDisplayDeviceConfig.getAmbientLightSensor().type);
+        assertEquals("", mDisplayDeviceConfig.getAmbientLightSensor().name);
+        assertNull(mDisplayDeviceConfig.getProximitySensor().type);
+        assertNull(mDisplayDeviceConfig.getProximitySensor().name);
+        assertTrue(mDisplayDeviceConfig.isAutoBrightnessAvailable());
+    }
+
+    @Test
     public void testConfigValuesFromDisplayConfig() throws IOException {
         setupDisplayDeviceConfigFromDisplayConfigFile();
 
@@ -681,6 +719,7 @@
 
         assertEquals("test_light_sensor", mDisplayDeviceConfig.getAmbientLightSensor().type);
         assertEquals("", mDisplayDeviceConfig.getAmbientLightSensor().name);
+        assertTrue(mDisplayDeviceConfig.isAutoBrightnessAvailable());
 
         assertEquals(brightnessIntToFloat(35),
                 mDisplayDeviceConfig.getBrightnessCapForWearBedtimeMode(), ZERO_DELTA);
@@ -807,6 +846,24 @@
                 mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), SMALL_DELTA);
     }
 
+    @Test
+    public void testIsAutoBrightnessAvailable_EnabledInConfigResource() throws IOException {
+        when(mResources.getBoolean(R.bool.config_automatic_brightness_available)).thenReturn(true);
+
+        setupDisplayDeviceConfigFromDisplayConfigFile();
+
+        assertTrue(mDisplayDeviceConfig.isAutoBrightnessAvailable());
+    }
+
+    @Test
+    public void testIsAutoBrightnessAvailable_DisabledInConfigResource() throws IOException {
+        when(mResources.getBoolean(R.bool.config_automatic_brightness_available)).thenReturn(false);
+
+        setupDisplayDeviceConfigFromDisplayConfigFile();
+
+        assertFalse(mDisplayDeviceConfig.isAutoBrightnessAvailable());
+    }
+
     private String getValidLuxThrottling() {
         return "<luxThrottling>\n"
                 + "    <brightnessLimitMap>\n"
@@ -1176,7 +1233,7 @@
                 +           "<nits>" + NITS[2] + "</nits>\n"
                 +       "</point>\n"
                 +   "</screenBrightnessMap>\n"
-                +   "<autoBrightness>\n"
+                +   "<autoBrightness enabled=\"true\">\n"
                 +       "<brighteningLightDebounceMillis>2000</brighteningLightDebounceMillis>\n"
                 +       "<darkeningLightDebounceMillis>1000</darkeningLightDebounceMillis>\n"
                 + (includeIdleMode ? getRampSpeedsIdle() : "")
@@ -1593,6 +1650,7 @@
 
         when(mResources.getString(com.android.internal.R.string.config_displayLightSensorType))
                 .thenReturn("test_light_sensor");
+        when(mResources.getBoolean(R.bool.config_automatic_brightness_available)).thenReturn(true);
 
         when(mResources.getInteger(
                 R.integer.config_autoBrightnessBrighteningLightDebounce))
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index f386c3b..d876dae 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -80,10 +80,13 @@
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
 
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
+import android.app.ApplicationExitInfo;
 import android.app.IApplicationThread;
 import android.app.IServiceConnection;
 import android.content.ComponentName;
@@ -94,9 +97,11 @@
 import android.os.Build;
 import android.os.IBinder;
 import android.os.PowerManagerInternal;
+import android.os.Process;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.ArrayMap;
 import android.util.SparseArray;
 
@@ -107,7 +112,9 @@
 
 import org.junit.After;
 import org.junit.AfterClass;
+import org.junit.Before;
 import org.junit.BeforeClass;
+import org.junit.Rule;
 import org.junit.Test;
 
 import java.io.File;
@@ -148,12 +155,18 @@
     private static final String MOCKAPP5_PROCESSNAME = "test #5";
     private static final String MOCKAPP5_PACKAGENAME = "com.android.test.test5";
     private static final int MOCKAPP2_UID_OTHER = MOCKAPP2_UID + UserHandle.PER_USER_RANGE;
+    private static final int MOCKAPP_ISOLATED_UID = Process.FIRST_ISOLATED_UID + 321;
+    private static final String MOCKAPP_ISOLATED_PROCESSNAME = "isolated test #1";
+
     private static int sFirstCachedAdj = ProcessList.CACHED_APP_MIN_ADJ
                                           + ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
     private static Context sContext;
     private static PackageManagerInternal sPackageManagerInternal;
     private static ActivityManagerService sService;
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @SuppressWarnings("GuardedBy")
     @BeforeClass
     public static void setUpOnce() {
@@ -220,6 +233,11 @@
         }
     }
 
+    @Before
+    public void setUp() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_NEW_FGS_RESTRICTION_LOGIC);
+    }
+
     @AfterClass
     public static void tearDownOnce() {
         LocalServices.removeServiceForTest(PackageManagerInternal.class);
@@ -286,6 +304,20 @@
     }
 
     /**
+     * Run updateOomAdjPendingTargetsLocked().
+     * - enqueues all provided processes to the pending list and lru before running
+     */
+    @SuppressWarnings("GuardedBy")
+    private void updateOomAdjPending(ProcessRecord... apps) {
+        setProcessesToLru(apps);
+        for (ProcessRecord app : apps) {
+            sService.mOomAdjuster.enqueueOomAdjTargetLocked(app);
+        }
+        sService.mOomAdjuster.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_NONE);
+        sService.mProcessList.getLruProcessesLOSP().clear();
+    }
+
+    /**
      * Fix up the pointers in the {@link ProcessRecordNode#mApp}:
      * because we used the mokito spy objects all over the tests here, but the internal
      * pointers in the {@link ProcessRecordNode#mApp} actually point to the real object.
@@ -519,6 +551,7 @@
         sService.mConstants.mShortFgsProcStateExtraWaitDuration = 200_000;
 
         ServiceRecord s = ServiceRecord.newEmptyInstanceForTest(sService);
+        s.appInfo = new ApplicationInfo();
         s.startRequested = true;
         s.isForeground = true;
         s.foregroundServiceType = FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
@@ -561,6 +594,7 @@
 
         // SHORT_SERVICE, timed out already.
         s = ServiceRecord.newEmptyInstanceForTest(sService);
+        s.appInfo = new ApplicationInfo();
         s.startRequested = true;
         s.isForeground = true;
         s.foregroundServiceType = FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
@@ -1079,6 +1113,7 @@
 
         // In order to trick OomAdjuster to think it has a short-service, we need this logic.
         ServiceRecord s = ServiceRecord.newEmptyInstanceForTest(sService);
+        s.appInfo = new ApplicationInfo();
         s.startRequested = true;
         s.isForeground = true;
         s.foregroundServiceType = FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
@@ -1109,6 +1144,7 @@
 
         // In order to trick OomAdjuster to think it has a short-service, we need this logic.
         ServiceRecord s = ServiceRecord.newEmptyInstanceForTest(sService);
+        s.appInfo = new ApplicationInfo();
         s.startRequested = true;
         s.isForeground = true;
         s.foregroundServiceType = FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
@@ -1400,6 +1436,7 @@
 
         // In order to trick OomAdjuster to think it has a short-service, we need this logic.
         ServiceRecord s = ServiceRecord.newEmptyInstanceForTest(sService);
+        s.appInfo = new ApplicationInfo();
         s.startRequested = true;
         s.isForeground = true;
         s.foregroundServiceType = FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
@@ -2651,6 +2688,90 @@
         assertNotEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
     }
 
+    @SuppressWarnings("GuardedBy")
+    @Test
+    public void testUpdateOomAdj_DoAll_Isolated_stopService() {
+        ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_ISOLATED_UID,
+                MOCKAPP_ISOLATED_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
+
+        setProcessesToLru(app);
+        ServiceRecord s = makeServiceRecord(app);
+        s.startRequested = true;
+        s.lastActivity = SystemClock.uptimeMillis();
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+        updateOomAdj();
+        assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_ADJ, SCHED_GROUP_BACKGROUND);
+
+        app.mServices.stopService(s);
+        updateOomAdj();
+        // isolated process should be killed immediately after service stop.
+        verify(app).killLocked("isolated not needed", ApplicationExitInfo.REASON_OTHER,
+                ApplicationExitInfo.SUBREASON_ISOLATED_NOT_NEEDED, true);
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
+    public void testUpdateOomAdj_DoPending_Isolated_stopService() {
+        ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_ISOLATED_UID,
+                MOCKAPP_ISOLATED_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
+
+        ServiceRecord s = makeServiceRecord(app);
+        s.startRequested = true;
+        s.lastActivity = SystemClock.uptimeMillis();
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+        updateOomAdjPending(app);
+        assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_ADJ, SCHED_GROUP_BACKGROUND);
+
+        app.mServices.stopService(s);
+        updateOomAdjPending(app);
+        // isolated process should be killed immediately after service stop.
+        verify(app).killLocked("isolated not needed", ApplicationExitInfo.REASON_OTHER,
+                ApplicationExitInfo.SUBREASON_ISOLATED_NOT_NEEDED, true);
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
+    public void testUpdateOomAdj_DoAll_Isolated_stopServiceWithEntryPoint() {
+        ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_ISOLATED_UID,
+                MOCKAPP_ISOLATED_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
+        app.setIsolatedEntryPoint("test");
+
+        setProcessesToLru(app);
+        ServiceRecord s = makeServiceRecord(app);
+        s.startRequested = true;
+        s.lastActivity = SystemClock.uptimeMillis();
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+        updateOomAdj();
+        assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_ADJ, SCHED_GROUP_BACKGROUND);
+
+        app.mServices.stopService(s);
+        updateOomAdj();
+        // isolated process with entry point should not be killed
+        verify(app, never()).killLocked("isolated not needed", ApplicationExitInfo.REASON_OTHER,
+                ApplicationExitInfo.SUBREASON_ISOLATED_NOT_NEEDED, true);
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
+    public void testUpdateOomAdj_DoPending_Isolated_stopServiceWithEntryPoint() {
+        ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_ISOLATED_UID,
+                MOCKAPP_ISOLATED_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
+        app.setIsolatedEntryPoint("test");
+
+        ServiceRecord s = makeServiceRecord(app);
+        s.startRequested = true;
+        s.lastActivity = SystemClock.uptimeMillis();
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+        updateOomAdjPending(app);
+        assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_ADJ, SCHED_GROUP_BACKGROUND);
+
+        app.mServices.stopService(s);
+        updateOomAdjPending(app);
+        // isolated process with entry point should not be killed
+        verify(app, never()).killLocked("isolated not needed", ApplicationExitInfo.REASON_OTHER,
+                ApplicationExitInfo.SUBREASON_ISOLATED_NOT_NEEDED, true);
+    }
+
     private ProcessRecord makeDefaultProcessRecord(int pid, int uid, String processName,
             String packageName, boolean hasShownUi) {
         long now = SystemClock.uptimeMillis();
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
index e5ecdc4..0403c64 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -210,6 +210,9 @@
                 anyInt())).thenReturn(1);
         when(mInstallerService.getExistingDraftSessionId(anyInt(), any(), anyInt())).thenReturn(
                 PackageInstaller.SessionInfo.INVALID_ID);
+        PackageInstallerSession session = mock(PackageInstallerSession.class);
+        when(mInstallerService.getSession(anyInt())).thenReturn(session);
+        when(session.getUnarchivalStatus()).thenReturn(PackageInstaller.UNARCHIVAL_STATUS_UNSET);
         doReturn(new ParceledListSlice<>(List.of(mock(ResolveInfo.class))))
                 .when(mPackageManagerService).queryIntentReceivers(any(), any(), any(), anyLong(),
                         eq(mUserId));
diff --git a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
index 97e94e3..37ca09d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
@@ -47,7 +47,6 @@
 import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
 import android.hardware.biometrics.BiometricManager;
-import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -280,7 +279,7 @@
                 "com.android/.SystemTrustAgent");
         addTrustAgent(newAgentComponentName, /* isSystemApp= */ true);
 
-        mMockContext.sendPackageChangedBroadcast(newAgentComponentName);
+        notifyPackageChanged(newAgentComponentName);
 
         assertThat(mEnabledTrustAgents).containsExactly(newAgentComponentName);
         assertThat(mKnownTrustAgents).containsExactly(newAgentComponentName);
@@ -299,7 +298,7 @@
                 "com.android/.SystemTrustAgent");
         addTrustAgent(newAgentComponentName, /* isSystemApp= */ true);
 
-        mMockContext.sendPackageChangedBroadcast(newAgentComponentName);
+        notifyPackageChanged(newAgentComponentName);
 
         assertThat(mEnabledTrustAgents).containsExactly(defaultTrustAgent);
         assertThat(mKnownTrustAgents).containsExactly(defaultTrustAgent, newAgentComponentName);
@@ -312,7 +311,7 @@
                 "com.user/.UserTrustAgent");
         addTrustAgent(newAgentComponentName, /* isSystemApp= */ false);
 
-        mMockContext.sendPackageChangedBroadcast(newAgentComponentName);
+        notifyPackageChanged(newAgentComponentName);
 
         assertThat(mEnabledTrustAgents).isEmpty();
         assertThat(mKnownTrustAgents).containsExactly(newAgentComponentName);
@@ -330,7 +329,7 @@
         // Simulate user turning off systemTrustAgent2
         mLockPatternUtils.setEnabledTrustAgents(List.of(systemTrustAgent1), TEST_USER_ID);
 
-        mMockContext.sendPackageChangedBroadcast(systemTrustAgent2);
+        notifyPackageChanged(systemTrustAgent2);
 
         assertThat(mEnabledTrustAgents).containsExactly(systemTrustAgent1);
     }
@@ -440,11 +439,16 @@
                 permission, PackageManager.PERMISSION_GRANTED);
     }
 
+    private void notifyPackageChanged(ComponentName changedComponent) {
+        mService.mPackageMonitor.onPackageChanged(
+                changedComponent.getPackageName(),
+                UserHandle.of(TEST_USER_ID).getUid(1234),
+                new String[] { changedComponent.getClassName() });
+    }
+
     /** A mock Context that allows the test process to send protected broadcasts. */
     private static final class MockContext extends TestableContext {
 
-        private final ArrayList<BroadcastReceiver> mPackageChangedBroadcastReceivers =
-                new ArrayList<>();
         private final ArrayList<BroadcastReceiver> mUserStartedBroadcastReceivers =
                 new ArrayList<>();
 
@@ -458,9 +462,6 @@
                 UserHandle user, IntentFilter filter, @Nullable String broadcastPermission,
                 @Nullable Handler scheduler) {
 
-            if (filter.hasAction(Intent.ACTION_PACKAGE_CHANGED)) {
-                mPackageChangedBroadcastReceivers.add(receiver);
-            }
             if (filter.hasAction(Intent.ACTION_USER_STARTED)) {
                 mUserStartedBroadcastReceivers.add(receiver);
             }
@@ -473,20 +474,6 @@
                 @Nullable String receiverPermission, @Nullable Bundle options) {
         }
 
-        void sendPackageChangedBroadcast(ComponentName changedComponent) {
-            Intent intent = new Intent(
-                    Intent.ACTION_PACKAGE_CHANGED,
-                    Uri.fromParts(URI_SCHEME_PACKAGE,
-                            changedComponent.getPackageName(), /* fragment= */ null))
-                    .putExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST,
-                            new String[]{changedComponent.getClassName()})
-                    .putExtra(Intent.EXTRA_USER_HANDLE, TEST_USER_ID)
-                    .putExtra(Intent.EXTRA_UID, UserHandle.of(TEST_USER_ID).getUid(1234));
-            for (BroadcastReceiver receiver : mPackageChangedBroadcastReceivers) {
-                receiver.onReceive(this, intent);
-            }
-        }
-
         void sendUserStartedBroadcast() {
             Intent intent = new Intent(Intent.ACTION_USER_STARTED)
                     .putExtra(Intent.EXTRA_USER_HANDLE, TEST_USER_ID);
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 9b84190..0045026 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -215,6 +215,16 @@
 }
 
 java_library {
+    name: "mockito-test-utils",
+    srcs: [
+        "utils-mockito/**/*.kt",
+    ],
+    static_libs: [
+        "mockito-target-minus-junit4",
+    ],
+}
+
+java_library {
     name: "servicestests-utils-mockito-extended",
     srcs: [
         "utils/**/*.java",
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 77b1455..26934d8 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -763,8 +763,7 @@
 
         mUserController.startUser(TEST_USER_ID, USER_START_MODE_BACKGROUND);
 
-        verify(mInjector.mStorageManagerMock, never())
-                .unlockCeStorage(eq(TEST_USER_ID), anyInt(), any());
+        verify(mInjector.mStorageManagerMock, never()).unlockCeStorage(eq(TEST_USER_ID), any());
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index 8929900..f7480de 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -44,12 +44,18 @@
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.fingerprint.Fingerprint;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableContext;
 import android.testing.TestableLooper;
@@ -60,6 +66,7 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.nano.BiometricSchedulerProto;
@@ -95,14 +102,39 @@
     @Rule
     public final TestableContext mContext = new TestableContext(
             InstrumentationRegistry.getContext(), null);
-    private BiometricScheduler mScheduler;
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+    private BiometricScheduler<IFingerprint, ISession> mScheduler;
     private IBinder mToken;
+    private int mCurrentUserId = UserHandle.USER_SYSTEM;
+    private boolean mShouldFailStopUser = false;
+    private final List<Integer> mStartedUsers = new ArrayList<>();
+    private final StartUserClient.UserStartedCallback<ISession> mUserStartedCallback =
+            (newUserId, newUser, halInterfaceVersion) -> {
+                mStartedUsers.add(newUserId);
+                mCurrentUserId = newUserId;
+            };
+    private int mUsersStoppedCount = 0;
+    private final StopUserClient.UserStoppedCallback mUserStoppedCallback =
+            () -> {
+                mUsersStoppedCount++;
+                mCurrentUserId = UserHandle.USER_NULL;
+            };
+    private boolean mStartOperationsFinish = true;
+    private int mStartUserClientCount = 0;
     @Mock
     private IBiometricService mBiometricService;
     @Mock
     private BiometricContext mBiometricContext;
     @Mock
     private AuthSessionCoordinator mAuthSessionCoordinator;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private ISession mSession;
+    @Mock
+    private IFingerprint mFingerprint;
 
     @Before
     public void setUp() {
@@ -111,9 +143,39 @@
         when(mAuthSessionCoordinator.getLockoutStateFor(anyInt(), anyInt())).thenReturn(
                 BIOMETRIC_SUCCESS);
         when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
-        mScheduler = new BiometricScheduler(TAG, new Handler(TestableLooper.get(this).getLooper()),
-                BiometricScheduler.SENSOR_TYPE_UNKNOWN, null /* gestureAvailabilityTracker */,
-                mBiometricService, LOG_NUM_RECENT_OPERATIONS);
+        if (Flags.deHidl()) {
+            mScheduler = new BiometricScheduler<>(
+                    new Handler(TestableLooper.get(this).getLooper()),
+                    BiometricScheduler.SENSOR_TYPE_UNKNOWN,
+                    null /* gestureAvailabilityDispatcher */,
+                    mBiometricService,
+                    LOG_NUM_RECENT_OPERATIONS,
+                    () -> mCurrentUserId,
+                    new UserSwitchProvider<IFingerprint, ISession>() {
+                        @NonNull
+                        @Override
+                        public StopUserClient<ISession> getStopUserClient(int userId) {
+                            return new TestStopUserClient(mContext, () -> mSession, mToken, userId,
+                                    TEST_SENSOR_ID, mBiometricLogger, mBiometricContext,
+                                    mUserStoppedCallback, () -> mShouldFailStopUser);
+                        }
+
+                        @NonNull
+                        @Override
+                        public StartUserClient<IFingerprint, ISession> getStartUserClient(
+                                int newUserId) {
+                            mStartUserClientCount++;
+                            return new TestStartUserClient(mContext, () -> mFingerprint, mToken,
+                                    newUserId, TEST_SENSOR_ID, mBiometricLogger, mBiometricContext,
+                                    mUserStartedCallback, mStartOperationsFinish);
+                        }
+                    });
+        } else {
+            mScheduler = new BiometricScheduler<>(
+                    new Handler(TestableLooper.get(this).getLooper()),
+                    BiometricScheduler.SENSOR_TYPE_UNKNOWN, null /* gestureAvailabilityTracker */,
+                    mBiometricService, LOG_NUM_RECENT_OPERATIONS);
+        }
     }
 
     @Test
@@ -479,6 +541,7 @@
         final boolean isEnroll = client instanceof TestEnrollClient;
 
         mScheduler.scheduleClientMonitor(client);
+        waitForIdle();
         if (started) {
             mScheduler.startPreparedClient(client.getCookie());
         }
@@ -789,6 +852,172 @@
         assertEquals(1,  client1.getFingerprints().size());
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void testScheduleOperation_whenNoUser() {
+        mCurrentUserId = UserHandle.USER_NULL;
+
+        final BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
+        when(nextClient.getTargetUserId()).thenReturn(0);
+
+        mScheduler.scheduleClientMonitor(nextClient);
+        waitForIdle();
+
+        assertThat(mUsersStoppedCount).isEqualTo(0);
+        assertThat(mStartedUsers).containsExactly(0);
+        verify(nextClient).start(any());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void testScheduleOperation_whenNoUser_notStarted() {
+        mCurrentUserId = UserHandle.USER_NULL;
+        mStartOperationsFinish = false;
+
+        final BaseClientMonitor[] nextClients = new BaseClientMonitor[]{
+                mock(BaseClientMonitor.class),
+                mock(BaseClientMonitor.class),
+                mock(BaseClientMonitor.class)
+        };
+        for (BaseClientMonitor client : nextClients) {
+            when(client.getTargetUserId()).thenReturn(5);
+            mScheduler.scheduleClientMonitor(client);
+            waitForIdle();
+        }
+
+        assertThat(mUsersStoppedCount).isEqualTo(0);
+        assertThat(mStartedUsers).isEmpty();
+        assertThat(mStartUserClientCount).isEqualTo(1);
+        for (BaseClientMonitor client : nextClients) {
+            verify(client, never()).start(any());
+        }
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void testScheduleOperation_whenNoUser_notStarted_andReset() {
+        mCurrentUserId = UserHandle.USER_NULL;
+        mStartOperationsFinish = false;
+
+        final BaseClientMonitor client = mock(BaseClientMonitor.class);
+
+        when(client.getTargetUserId()).thenReturn(5);
+
+        mScheduler.scheduleClientMonitor(client);
+        waitForIdle();
+
+        final TestStartUserClient startUserClient =
+                (TestStartUserClient) mScheduler.mCurrentOperation.getClientMonitor();
+        mScheduler.reset();
+
+        assertThat(mScheduler.mCurrentOperation).isNull();
+
+        final BiometricSchedulerOperation fakeOperation = new BiometricSchedulerOperation(
+                mock(BaseClientMonitor.class), new ClientMonitorCallback() {});
+        mScheduler.mCurrentOperation = fakeOperation;
+        startUserClient.mCallback.onClientFinished(startUserClient, true);
+
+        assertThat(fakeOperation).isSameInstanceAs(mScheduler.mCurrentOperation);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void testScheduleOperation_whenSameUser() {
+        mCurrentUserId = 10;
+
+        BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
+        when(nextClient.getTargetUserId()).thenReturn(mCurrentUserId);
+
+        mScheduler.scheduleClientMonitor(nextClient);
+
+        waitForIdle();
+
+        verify(nextClient).start(any());
+        assertThat(mUsersStoppedCount).isEqualTo(0);
+        assertThat(mStartedUsers).isEmpty();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void testScheduleOperation_whenDifferentUser() {
+        mCurrentUserId = 10;
+
+        final int nextUserId = 11;
+        BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
+        when(nextClient.getTargetUserId()).thenReturn(nextUserId);
+
+        mScheduler.scheduleClientMonitor(nextClient);
+
+        waitForIdle();
+        assertThat(mUsersStoppedCount).isEqualTo(1);
+
+        waitForIdle();
+        assertThat(mStartedUsers).containsExactly(nextUserId);
+
+        waitForIdle();
+        verify(nextClient).start(any());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void testStartUser_alwaysStartsNextOperation() {
+        mCurrentUserId = UserHandle.USER_NULL;
+
+        BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
+        when(nextClient.getTargetUserId()).thenReturn(10);
+
+        mScheduler.scheduleClientMonitor(nextClient);
+
+        waitForIdle();
+        verify(nextClient).start(any());
+
+        // finish first operation
+        mScheduler.getInternalCallback().onClientFinished(nextClient, true /* success */);
+        waitForIdle();
+
+        // schedule second operation but swap out the current operation
+        // before it runs so that it's not current when it's completion callback runs
+        nextClient = mock(BaseClientMonitor.class);
+        when(nextClient.getTargetUserId()).thenReturn(11);
+        mScheduler.scheduleClientMonitor(nextClient);
+
+        waitForIdle();
+        verify(nextClient).start(any());
+        assertThat(mStartedUsers).containsExactly(10, 11).inOrder();
+        assertThat(mUsersStoppedCount).isEqualTo(1);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void testStartUser_failsClearsStopUserClient() {
+        mCurrentUserId = UserHandle.USER_NULL;
+
+        // When a stop user client fails, check that mStopUserClient
+        // is set to null to prevent the scheduler from getting stuck.
+        BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
+        when(nextClient.getTargetUserId()).thenReturn(10);
+
+        mScheduler.scheduleClientMonitor(nextClient);
+
+        waitForIdle();
+        verify(nextClient).start(any());
+
+        // finish first operation
+        mScheduler.getInternalCallback().onClientFinished(nextClient, true /* success */);
+        waitForIdle();
+
+        // schedule second operation but swap out the current operation
+        // before it runs so that it's not current when it's completion callback runs
+        nextClient = mock(BaseClientMonitor.class);
+        when(nextClient.getTargetUserId()).thenReturn(11);
+        mShouldFailStopUser = true;
+        mScheduler.scheduleClientMonitor(nextClient);
+
+        waitForIdle();
+        assertThat(mStartedUsers).containsExactly(10, 11).inOrder();
+        assertThat(mUsersStoppedCount).isEqualTo(0);
+        assertThat(mScheduler.getStopUserClient()).isEqualTo(null);
+    }
 
     private BiometricSchedulerProto getDump(boolean clearSchedulerBuffer) throws Exception {
         return BiometricSchedulerProto.parseFrom(mScheduler.dumpProtoState(clearSchedulerBuffer));
@@ -1069,4 +1298,82 @@
             return mFingerprints;
         }
     }
+
+    private interface StopUserClientShouldFail {
+        boolean shouldFail();
+    }
+
+    private class TestStopUserClient extends StopUserClient<ISession> {
+        private StopUserClientShouldFail mShouldFailClient;
+        TestStopUserClient(@NonNull Context context,
+                @NonNull Supplier<ISession> lazyDaemon, @Nullable IBinder token, int userId,
+                int sensorId, @NonNull BiometricLogger logger,
+                @NonNull BiometricContext biometricContext,
+                @NonNull UserStoppedCallback callback, StopUserClientShouldFail shouldFail) {
+            super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
+            mShouldFailClient = shouldFail;
+        }
+
+        @Override
+        protected void startHalOperation() {
+
+        }
+
+        @Override
+        public void start(@NonNull ClientMonitorCallback callback) {
+            super.start(callback);
+            if (mShouldFailClient.shouldFail()) {
+                getCallback().onClientFinished(this, false /* success */);
+                // When the above fails, it means that the HAL has died, in this case we
+                // need to ensure the UserSwitchCallback correctly returns the NULL user handle.
+                mCurrentUserId = UserHandle.USER_NULL;
+            } else {
+                onUserStopped();
+            }
+        }
+
+        @Override
+        public void unableToStart() {
+
+        }
+    }
+
+    private static class TestStartUserClient extends StartUserClient<IFingerprint, ISession> {
+
+        @Mock
+        private ISession mSession;
+        private final boolean mShouldFinish;
+        ClientMonitorCallback mCallback;
+
+        TestStartUserClient(@NonNull Context context,
+                @NonNull Supplier<IFingerprint> lazyDaemon, @Nullable IBinder token, int userId,
+                int sensorId, @NonNull BiometricLogger logger,
+                @NonNull BiometricContext biometricContext,
+                @NonNull UserStartedCallback<ISession> callback, boolean shouldFinish) {
+            super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
+            mShouldFinish = shouldFinish;
+        }
+
+        @Override
+        protected void startHalOperation() {
+
+        }
+
+        @Override
+        public void start(@NonNull ClientMonitorCallback callback) {
+            super.start(callback);
+
+            mCallback = callback;
+            if (mShouldFinish) {
+                mUserStartedCallback.onUserStarted(
+                        getTargetUserId(), mSession, 1 /* halInterfaceVersion */);
+                callback.onClientFinished(this, true /* success */);
+            }
+        }
+
+        @Override
+        public void unableToStart() {
+
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index 8b1a291..772ec8b 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -36,8 +36,10 @@
 import android.hardware.biometrics.face.ISession;
 import android.hardware.biometrics.face.SensorProps;
 import android.hardware.face.HidlFaceSensorConfig;
+import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserManager;
+import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
@@ -88,14 +90,11 @@
     @Mock
     private BiometricStateCallback mBiometricStateCallback;
 
+    private final TestLooper mLooper = new TestLooper();
     private SensorProps[] mSensorProps;
     private LockoutResetDispatcher mLockoutResetDispatcher;
     private FaceProvider mFaceProvider;
 
-    private static void waitForIdle() {
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-    }
-
     @Before
     public void setUp() throws RemoteException {
         MockitoAnnotations.initMocks(this);
@@ -121,7 +120,8 @@
 
         mFaceProvider = new FaceProvider(mContext, mBiometricStateCallback,
                 mSensorProps, TAG, mLockoutResetDispatcher, mBiometricContext,
-                mDaemon, false /* resetLockoutRequiresChallenge */, false /* testHalEnabled */);
+                mDaemon, new Handler(mLooper.getLooper()),
+                false /* resetLockoutRequiresChallenge */, false /* testHalEnabled */);
     }
 
     @Test
@@ -156,6 +156,7 @@
         mFaceProvider = new FaceProvider(mContext,
                 mBiometricStateCallback, hidlFaceSensorConfig, TAG,
                 mLockoutResetDispatcher, mBiometricContext, mDaemon,
+                new Handler(mLooper.getLooper()),
                 true /* resetLockoutRequiresChallenge */,
                 true /* testHalEnabled */);
 
@@ -210,4 +211,12 @@
             assertEquals(0, scheduler.getCurrentPendingCount());
         }
     }
+
+    private void waitForIdle() {
+        if (Flags.deHidl()) {
+            mLooper.dispatchAll();
+        } else {
+            InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
index e7f7195..fe9cd43 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
@@ -31,6 +31,7 @@
 import android.content.Context;
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.common.CommonProps;
+import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.ISession;
 import android.hardware.biometrics.face.SensorProps;
 import android.hardware.face.FaceSensorPropertiesInternal;
@@ -40,6 +41,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
@@ -49,6 +51,7 @@
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
+import com.android.server.biometrics.sensors.UserSwitchProvider;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -74,6 +77,8 @@
     @Mock
     private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback;
     @Mock
+    private UserSwitchProvider<IFace, ISession> mUserSwitchProvider;
+    @Mock
     private AidlResponseHandler.HardwareUnavailableCallback mHardwareUnavailableCallback;
     @Mock
     private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -84,16 +89,16 @@
     @Mock
     private AuthSessionCoordinator mAuthSessionCoordinator;
     @Mock
-    FaceProvider mFaceProvider;
+    private FaceProvider mFaceProvider;
     @Mock
-    BaseClientMonitor mClientMonitor;
+    private BaseClientMonitor mClientMonitor;
     @Mock
-    AidlSession mCurrentSession;
+    private AidlSession mCurrentSession;
 
     private final TestLooper mLooper = new TestLooper();
     private final LockoutCache mLockoutCache = new LockoutCache();
 
-    private UserAwareBiometricScheduler mScheduler;
+    private BiometricScheduler<IFace, ISession> mScheduler;
     private AidlResponseHandler mHalCallback;
 
     @Before
@@ -101,16 +106,26 @@
         MockitoAnnotations.initMocks(this);
 
         when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService);
-
         when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
 
-        mScheduler = new UserAwareBiometricScheduler(TAG,
-                new Handler(mLooper.getLooper()),
-                BiometricScheduler.SENSOR_TYPE_FACE,
-                null /* gestureAvailabilityDispatcher */,
-                mBiometricService,
-                () -> USER_ID,
-                mUserSwitchCallback);
+        if (Flags.deHidl()) {
+            mScheduler = new BiometricScheduler<>(
+                    new Handler(mLooper.getLooper()),
+                    BiometricScheduler.SENSOR_TYPE_FACE,
+                    null /* gestureAvailabilityDispatcher */,
+                    mBiometricService,
+                    2 /* recentOperationsLimit */,
+                    () -> USER_ID,
+                    mUserSwitchProvider);
+        } else {
+            mScheduler = new UserAwareBiometricScheduler<>(TAG,
+                    new Handler(mLooper.getLooper()),
+                    BiometricScheduler.SENSOR_TYPE_FACE,
+                    null /* gestureAvailabilityDispatcher */,
+                    mBiometricService,
+                    () -> USER_ID,
+                    mUserSwitchCallback);
+        }
         mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID,
                 mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
                 mHardwareUnavailableCallback);
@@ -146,18 +161,8 @@
     @Test
     public void onBinderDied_noErrorOnNullClient() {
         mLooper.dispatchAll();
-
-        final SensorProps sensorProps = new SensorProps();
-        sensorProps.commonProps = new CommonProps();
-        sensorProps.commonProps.sensorId = 1;
-        final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
-                sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength,
-                sensorProps.commonProps.maxEnrollmentsPerUser, null /* componentInfo */,
-                sensorProps.sensorType, sensorProps.supportsDetectInteraction,
-                sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */);
-        final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext,
-                null /* handler */, internalProp, mLockoutResetDispatcher, mBiometricContext);
-        sensor.init(mLockoutResetDispatcher, mFaceProvider);
+        final Sensor sensor = getSensor();
+        mScheduler = sensor.getScheduler();
         mScheduler.reset();
 
         assertNull(mScheduler.getCurrentClient());
@@ -175,18 +180,8 @@
         when(mClientMonitor.getTargetUserId()).thenReturn(USER_ID);
         when(mClientMonitor.isInterruptable()).thenReturn(false);
 
-        final SensorProps sensorProps = new SensorProps();
-        sensorProps.commonProps = new CommonProps();
-        sensorProps.commonProps.sensorId = 1;
-        final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
-                sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength,
-                sensorProps.commonProps.maxEnrollmentsPerUser, null /* componentInfo */,
-                sensorProps.sensorType, sensorProps.supportsDetectInteraction,
-                sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */);
-        final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, null,
-                internalProp, mLockoutResetDispatcher, mBiometricContext, mCurrentSession);
-        sensor.init(mLockoutResetDispatcher, mFaceProvider);
-        mScheduler = (UserAwareBiometricScheduler) sensor.getScheduler();
+        final Sensor sensor = getSensor();
+        mScheduler = sensor.getScheduler();
         sensor.mCurrentSession = new AidlSession(0, mock(ISession.class),
                 USER_ID, mHalCallback);
 
@@ -206,4 +201,20 @@
         verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(eq(SENSOR_ID));
         verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
     }
+
+    private Sensor getSensor() {
+        final SensorProps sensorProps = new SensorProps();
+        sensorProps.commonProps = new CommonProps();
+        sensorProps.commonProps.sensorId = 1;
+        final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
+                sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength,
+                sensorProps.commonProps.maxEnrollmentsPerUser, null /* componentInfo */,
+                sensorProps.sensorType, sensorProps.supportsDetectInteraction,
+                sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */);
+        final Sensor sensor = new Sensor(mFaceProvider, mContext,
+                null /* handler */, internalProp, mBiometricContext);
+        sensor.init(mLockoutResetDispatcher, mFaceProvider);
+
+        return sensor;
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java
index 4e43332..940fe69 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java
@@ -45,7 +45,6 @@
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
-import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
@@ -88,9 +87,9 @@
     @Mock
     private IBiometricsFace mDaemon;
     @Mock
-    AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+    private AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
     @Mock
-    BiometricUtils<Face> mBiometricUtils;
+    private BiometricUtils<Face> mBiometricUtils;
 
     private final TestLooper mLooper = new TestLooper();
     private HidlToAidlSensorAdapter mHidlToAidlSensorAdapter;
@@ -118,20 +117,14 @@
         mContext.getOrCreateTestableResources();
 
         final String config = String.format("%d:8:15", SENSOR_ID);
-        final BiometricScheduler scheduler = new BiometricScheduler(TAG,
-                new Handler(mLooper.getLooper()),
-                BiometricScheduler.SENSOR_TYPE_FACE,
-                null /* gestureAvailabilityTracker */,
-                mBiometricService, 10 /* recentOperationsLimit */);
         final HidlFaceSensorConfig faceSensorConfig = new HidlFaceSensorConfig();
         faceSensorConfig.parse(config, mContext);
-        mHidlToAidlSensorAdapter = new HidlToAidlSensorAdapter(TAG, mFaceProvider,
+        mHidlToAidlSensorAdapter = new HidlToAidlSensorAdapter(mFaceProvider,
                 mContext, new Handler(mLooper.getLooper()), faceSensorConfig,
                 mLockoutResetDispatcherForSensor, mBiometricContext,
                 false /* resetLockoutRequiresChallenge */, mInternalCleanupAndGetFeatureRunnable,
                 mAuthSessionCoordinator, mDaemon, mAidlResponseHandlerCallback);
         mHidlToAidlSensorAdapter.init(mLockoutResetDispatcherForSensor, mFaceProvider);
-        mHidlToAidlSensorAdapter.setScheduler(scheduler);
         mHidlToAidlSensorAdapter.handleUserChanged(USER_ID);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index bf5986c..258be57 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
@@ -38,8 +38,10 @@
 import android.hardware.biometrics.fingerprint.SensorLocation;
 import android.hardware.biometrics.fingerprint.SensorProps;
 import android.hardware.fingerprint.HidlFingerprintSensorConfig;
+import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserManager;
+import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
@@ -92,14 +94,12 @@
     @Mock
     private BiometricContext mBiometricContext;
 
+    private final TestLooper mLooper = new TestLooper();
+
     private SensorProps[] mSensorProps;
     private LockoutResetDispatcher mLockoutResetDispatcher;
     private FingerprintProvider mFingerprintProvider;
 
-    private static void waitForIdle() {
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-    }
-
     @Before
     public void setUp() throws RemoteException {
         MockitoAnnotations.initMocks(this);
@@ -126,7 +126,8 @@
         mFingerprintProvider = new FingerprintProvider(mContext,
                 mBiometricStateCallback, mAuthenticationStateListeners, mSensorProps, TAG,
                 mLockoutResetDispatcher, mGestureAvailabilityDispatcher, mBiometricContext,
-                mDaemon, false /* resetLockoutRequiresHardwareAuthToken */,
+                mDaemon, new Handler(mLooper.getLooper()),
+                false /* resetLockoutRequiresHardwareAuthToken */,
                 true /* testHalEnabled */);
     }
 
@@ -159,6 +160,7 @@
                 mBiometricStateCallback, mAuthenticationStateListeners,
                 hidlFingerprintSensorConfigs, TAG, mLockoutResetDispatcher,
                 mGestureAvailabilityDispatcher, mBiometricContext, mDaemon,
+                new Handler(mLooper.getLooper()),
                 false /* resetLockoutRequiresHardwareAuthToken */,
                 true /* testHalEnabled */);
 
@@ -215,4 +217,12 @@
             assertEquals(0, scheduler.getCurrentPendingCount());
         }
     }
+
+    private void waitForIdle() {
+        if (Flags.deHidl()) {
+            mLooper.dispatchAll();
+        } else {
+            InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
index 126a05e..b4c2ee8 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
@@ -32,14 +32,17 @@
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.common.CommonProps;
 import android.hardware.biometrics.face.SensorProps;
+import android.hardware.biometrics.fingerprint.IFingerprint;
 import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
@@ -49,6 +52,7 @@
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
+import com.android.server.biometrics.sensors.UserSwitchProvider;
 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
 
 import org.junit.Before;
@@ -75,6 +79,8 @@
     @Mock
     private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback;
     @Mock
+    private UserSwitchProvider<IFingerprint, ISession> mUserSwitchProvider;
+    @Mock
     private AidlResponseHandler.HardwareUnavailableCallback mHardwareUnavailableCallback;
     @Mock
     private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -92,11 +98,13 @@
     private AidlSession mCurrentSession;
     @Mock
     private BaseClientMonitor mClientMonitor;
+    @Mock
+    private HandlerThread mThread;
 
     private final TestLooper mLooper = new TestLooper();
     private final LockoutCache mLockoutCache = new LockoutCache();
 
-    private BiometricScheduler mScheduler;
+    private BiometricScheduler<IFingerprint, ISession> mScheduler;
     private AidlResponseHandler mHalCallback;
 
     @Before
@@ -105,14 +113,26 @@
 
         when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService);
         when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
+        when(mThread.getLooper()).thenReturn(mLooper.getLooper());
 
-        mScheduler = new UserAwareBiometricScheduler(TAG,
-                new Handler(mLooper.getLooper()),
-                BiometricScheduler.SENSOR_TYPE_FP_OTHER,
-                null /* gestureAvailabilityDispatcher */,
-                mBiometricService,
-                () -> USER_ID,
-                mUserSwitchCallback);
+        if (Flags.deHidl()) {
+            mScheduler = new BiometricScheduler<>(
+                    new Handler(mLooper.getLooper()),
+                    BiometricScheduler.SENSOR_TYPE_FP_OTHER,
+                    null /* gestureAvailabilityDispatcher */,
+                    mBiometricService,
+                    2 /* recentOperationsLimit */,
+                    () -> USER_ID,
+                    mUserSwitchProvider);
+        } else {
+            mScheduler = new UserAwareBiometricScheduler<>(TAG,
+                    new Handler(mLooper.getLooper()),
+                    BiometricScheduler.SENSOR_TYPE_FP_OTHER,
+                    null /* gestureAvailabilityDispatcher */,
+                    mBiometricService,
+                    () -> USER_ID,
+                    mUserSwitchCallback);
+        }
         mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID,
                 mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
                 mHardwareUnavailableCallback);
@@ -153,18 +173,7 @@
         when(mClientMonitor.getTargetUserId()).thenReturn(USER_ID);
         when(mClientMonitor.isInterruptable()).thenReturn(false);
 
-        final SensorProps sensorProps = new SensorProps();
-        sensorProps.commonProps = new CommonProps();
-        sensorProps.commonProps.sensorId = 1;
-        final FingerprintSensorPropertiesInternal internalProp = new
-                FingerprintSensorPropertiesInternal(
-                        sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength,
-                        sensorProps.commonProps.maxEnrollmentsPerUser, null,
-                        sensorProps.sensorType, false /* resetLockoutRequiresHardwareAuthToken */);
-        final Sensor sensor = new Sensor("SensorTest", mFingerprintProvider, mContext,
-                null /* handler */, internalProp, mLockoutResetDispatcher,
-                mGestureAvailabilityDispatcher, mBiometricContext, mCurrentSession);
-        sensor.init(mGestureAvailabilityDispatcher, mLockoutResetDispatcher);
+        final Sensor sensor = getSensor();
         mScheduler = sensor.getScheduler();
         sensor.mCurrentSession = new AidlSession(0, mock(ISession.class),
                 USER_ID, mHalCallback);
@@ -185,4 +194,21 @@
         verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(eq(SENSOR_ID));
         verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
     }
+
+    private Sensor getSensor() {
+        final SensorProps sensorProps = new SensorProps();
+        sensorProps.commonProps = new CommonProps();
+        sensorProps.commonProps.sensorId = 1;
+        final FingerprintSensorPropertiesInternal internalProp = new
+                FingerprintSensorPropertiesInternal(
+                sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength,
+                sensorProps.commonProps.maxEnrollmentsPerUser, null,
+                sensorProps.sensorType, false /* resetLockoutRequiresHardwareAuthToken */);
+        final Sensor sensor = new Sensor(mFingerprintProvider, mContext,
+                null /* handler */, internalProp,
+                mBiometricContext, mCurrentSession);
+        sensor.init(mGestureAvailabilityDispatcher, mLockoutResetDispatcher);
+
+        return sensor;
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java
index 89a4961..cbbc545 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java
@@ -36,12 +36,12 @@
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.HidlFingerprintSensorConfig;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.RemoteException;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
 import android.testing.TestableContext;
 
-import androidx.annotation.NonNull;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.SmallTest;
 
@@ -49,17 +49,11 @@
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.AuthenticationStateListeners;
-import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricUtils;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
-import com.android.server.biometrics.sensors.StartUserClient;
-import com.android.server.biometrics.sensors.StopUserClient;
-import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
 import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
-import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession;
 import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintEnrollClient;
 import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider;
 import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintResetLockoutClient;
@@ -111,60 +105,18 @@
     private BiometricUtils<Fingerprint> mBiometricUtils;
     @Mock
     private AuthenticationStateListeners mAuthenticationStateListeners;
+    @Mock
+    private HandlerThread mThread;
 
     private final TestLooper mLooper = new TestLooper();
     private HidlToAidlSensorAdapter mHidlToAidlSensorAdapter;
     private final TestableContext mContext = new TestableContext(
             ApplicationProvider.getApplicationContext());
 
-    private final UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback =
-            new UserAwareBiometricScheduler.UserSwitchCallback() {
-                @NonNull
-                @Override
-                public StopUserClient<?> getStopUserClient(int userId) {
-                    return new StopUserClient<IBiometricsFingerprint>(mContext,
-                            mHidlToAidlSensorAdapter::getIBiometricsFingerprint, null, USER_ID,
-                            SENSOR_ID, mLogger, mBiometricContext, () -> {}) {
-                        @Override
-                        protected void startHalOperation() {
-                            getCallback().onClientFinished(this, true /* success */);
-                        }
-
-                        @Override
-                        public void unableToStart() {}
-                    };
-                }
-
-                @NonNull
-                @Override
-                public StartUserClient<?, ?> getStartUserClient(int newUserId) {
-                    return new StartUserClient<IBiometricsFingerprint, AidlSession>(mContext,
-                            mHidlToAidlSensorAdapter::getIBiometricsFingerprint, null,
-                            USER_ID, SENSOR_ID,
-                            mLogger, mBiometricContext,
-                            (newUserId1, newUser, halInterfaceVersion) ->
-                                    mHidlToAidlSensorAdapter.handleUserChanged(newUserId1)) {
-                        @Override
-                        public void start(@NonNull ClientMonitorCallback callback) {
-                            super.start(callback);
-                            startHalOperation();
-                        }
-
-                        @Override
-                        protected void startHalOperation() {
-                            mUserStartedCallback.onUserStarted(USER_ID, null, 0);
-                            getCallback().onClientFinished(this, true /* success */);
-                        }
-
-                        @Override
-                        public void unableToStart() {}
-                    };
-                }
-            };;
-
     @Before
     public void setUp() throws RemoteException {
         when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
+        when(mThread.getLooper()).thenReturn(mLooper.getLooper());
         doAnswer((answer) -> {
             mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback()
                     .onEnrollmentProgress(1 /* enrollmentId */, 0 /* remaining */);
@@ -175,26 +127,18 @@
         mContext.getOrCreateTestableResources();
 
         final String config = String.format("%d:2:15", SENSOR_ID);
-        final UserAwareBiometricScheduler scheduler = new UserAwareBiometricScheduler(TAG,
-                new Handler(mLooper.getLooper()),
-                BiometricScheduler.SENSOR_TYPE_FP_OTHER,
-                null /* gestureAvailabilityDispatcher */,
-                mBiometricService,
-                () -> USER_ID,
-                mUserSwitchCallback);
         final HidlFingerprintSensorConfig fingerprintSensorConfig =
                 new HidlFingerprintSensorConfig();
         fingerprintSensorConfig.parse(config, mContext);
-        mHidlToAidlSensorAdapter = new HidlToAidlSensorAdapter(TAG,
+        mHidlToAidlSensorAdapter = new HidlToAidlSensorAdapter(
                 mFingerprintProvider, mContext, new Handler(mLooper.getLooper()),
                 fingerprintSensorConfig, mLockoutResetDispatcherForSensor,
-                mGestureAvailabilityDispatcher, mBiometricContext,
-                false /* resetLockoutRequiresHardwareAuthToken */,
+                mBiometricContext, false /* resetLockoutRequiresHardwareAuthToken */,
                 mInternalCleanupRunnable, mAuthSessionCoordinator, mDaemon,
                 mAidlResponseHandlerCallback);
         mHidlToAidlSensorAdapter.init(mGestureAvailabilityDispatcher,
                 mLockoutResetDispatcherForSensor);
-        mHidlToAidlSensorAdapter.setScheduler(scheduler);
+
         mHidlToAidlSensorAdapter.handleUserChanged(USER_ID);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index f5d50d1..6986cab 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -305,9 +305,9 @@
         doAnswer(invocation -> {
             Object[] args = invocation.getArguments();
             mStorageManager.unlockCeStorage(/* userId= */ (int) args[0],
-                    /* secret= */ (byte[]) args[2]);
+                    /* secret= */ (byte[]) args[1]);
             return null;
-        }).when(sm).unlockCeStorage(anyInt(), anyInt(), any());
+        }).when(sm).unlockCeStorage(anyInt(), any());
 
         doAnswer(invocation -> {
             Object[] args = invocation.getArguments();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
index 42ad73a..8622488 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -158,6 +158,9 @@
         when(mAudioManager.isAudioFocusExclusive()).thenReturn(false);
         when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer);
         when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10);
+        // consistent with focus not exclusive and volume not muted
+        when(mAudioManager.shouldNotificationSoundPlay(any(AudioAttributes.class)))
+                .thenReturn(true);
         when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
         when(mAudioManager.getFocusRampTimeMs(anyInt(), any(AudioAttributes.class))).thenReturn(50);
         when(mUsageStats.isAlertRateLimited(any())).thenReturn(false);
@@ -869,6 +872,7 @@
         // the phone is quiet
         when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
         when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0);
+        when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(false);
 
         mService.buzzBeepBlinkLocked(r);
 
@@ -886,6 +890,8 @@
         // the phone is quiet
         when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
         when(mAudioManager.getStreamVolume(anyInt())).thenReturn(1);
+        // all streams at 1 means no muting from audio framework
+        when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(true);
 
         mService.buzzBeepBlinkLocked(r);
 
@@ -904,6 +910,7 @@
         // the phone is quiet
         when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
         when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0);
+        when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(false);
 
         mService.buzzBeepBlinkLocked(r);
 
@@ -924,6 +931,7 @@
         // the phone is quiet
         when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0);
         when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
+        when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(false);
 
         mService.buzzBeepBlinkLocked(r);
 
@@ -1195,6 +1203,7 @@
         // the phone is quiet
         when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0);
         when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
+        when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(false);
 
         mService.buzzBeepBlinkLocked(r);
         verifyDelayedVibrate(mService.getVibratorHelper().createFallbackVibration(false));
@@ -1923,6 +1932,7 @@
         NotificationRecord r = getBuzzyBeepyNotification();
         when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT);
         when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0);
+        when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(false);
 
         mService.buzzBeepBlinkLocked(r);
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index 1b77b99..bfd2df2d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -182,6 +182,8 @@
         when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10);
         when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
         when(mAudioManager.getFocusRampTimeMs(anyInt(), any(AudioAttributes.class))).thenReturn(50);
+        when(mAudioManager.shouldNotificationSoundPlay(any(AudioAttributes.class)))
+                .thenReturn(true);
         when(mUsageStats.isAlertRateLimited(any())).thenReturn(false);
         when(mVibrator.hasFrequencyControl()).thenReturn(false);
         when(mKeyguardManager.isDeviceLocked(anyInt())).thenReturn(false);
@@ -930,6 +932,8 @@
         // the phone is quiet
         when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
         when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0);
+        when(mAudioManager.shouldNotificationSoundPlay(any(AudioAttributes.class)))
+                .thenReturn(false);
 
         mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
 
@@ -947,6 +951,8 @@
         // the phone is quiet
         when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
         when(mAudioManager.getStreamVolume(anyInt())).thenReturn(1);
+        // all streams at 1 means no muting from audio framework
+        when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(true);
 
         mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
 
@@ -965,6 +971,7 @@
         // the phone is quiet
         when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
         when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0);
+        when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(false);
 
         mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
 
@@ -986,6 +993,7 @@
         // the phone is quiet
         when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0);
         when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
+        when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(false);
 
         mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
 
@@ -1258,6 +1266,7 @@
         // the phone is quiet
         when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0);
         when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
+        when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(false);
 
         mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
         verifyDelayedVibrate(mAttentionHelper.getVibratorHelper().createFallbackVibration(false));
@@ -1988,6 +1997,7 @@
         NotificationRecord r = getBuzzyBeepyNotification();
         when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT);
         when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0);
+        when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(false);
 
         mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 0514943..51df1d4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -195,6 +195,36 @@
     }
 
     @Test
+    public void testCreateInfo_Activity() {
+        final Transition transition = createTestTransition(TRANSIT_OPEN);
+        ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+        ArraySet<WindowContainer> participants = transition.mParticipants;
+
+        final Task theTask = createTask(mDisplayContent);
+        final ActivityRecord closing = createActivityRecord(theTask);
+        final ActivityRecord opening = createActivityRecord(theTask);
+        // Start states.
+        changes.put(theTask, new Transition.ChangeInfo(theTask, true /* vis */, false /* exChg */));
+        changes.put(opening, new Transition.ChangeInfo(opening, false /* vis */, true /* exChg */));
+        changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, false /* exChg */));
+        fillChangeMap(changes, theTask);
+        // End states.
+        closing.setVisibleRequested(false);
+        opening.setVisibleRequested(true);
+
+        final int transit = transition.mType;
+        int flags = 0;
+
+        participants.add(opening);
+        participants.add(closing);
+        ArrayList<Transition.ChangeInfo> targets =
+                Transition.calculateTargets(participants, changes);
+        TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
+        assertEquals(2, info.getChanges().size());
+        assertEquals(info.getChanges().get(1).getActivityComponent(), closing.mActivityComponent);
+    }
+
+    @Test
     public void testCreateInfo_NestedTasks() {
         final Transition transition = createTestTransition(TRANSIT_OPEN);
         ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;