Merge "Avoid NPE when removing fixed rotation transform" into udc-dev
diff --git a/Android.bp b/Android.bp
index cff863b..64d2c66 100644
--- a/Android.bp
+++ b/Android.bp
@@ -410,6 +410,7 @@
         "spatializer-aidl-java",
         "audiopolicy-aidl-java",
         "sounddose-aidl-java",
+        "modules-utils-expresslog",
     ],
 }
 
diff --git a/apct-tests/perftests/blobstore/Android.bp b/apct-tests/perftests/blobstore/Android.bp
index 9064b44..2590fe3 100644
--- a/apct-tests/perftests/blobstore/Android.bp
+++ b/apct-tests/perftests/blobstore/Android.bp
@@ -29,7 +29,7 @@
         "androidx.test.rules",
         "androidx.annotation_annotation",
         "apct-perftests-utils",
-        "ub-uiautomator",
+        "androidx.test.uiautomator_uiautomator",
         "collector-device-lib-platform",
         "androidx.benchmark_benchmark-macro",
     ],
diff --git a/apct-tests/perftests/blobstore/src/com/android/perftests/blob/AtraceUtils.java b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/AtraceUtils.java
index 0208dab..4e4780f 100644
--- a/apct-tests/perftests/blobstore/src/com/android/perftests/blob/AtraceUtils.java
+++ b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/AtraceUtils.java
@@ -21,10 +21,10 @@
 import android.os.ParcelFileDescriptor;
 import android.perftests.utils.TraceMarkParser;
 import android.perftests.utils.TraceMarkParser.TraceMarkSlice;
-import android.support.test.uiautomator.UiDevice;
 import android.util.Log;
 
 import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.UiDevice;
 
 import java.io.BufferedReader;
 import java.io.IOException;
diff --git a/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java
index 03e5468..3cd9f50 100644
--- a/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java
+++ b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java
@@ -25,12 +25,12 @@
 import android.perftests.utils.PerfManualStatusReporter;
 import android.perftests.utils.TraceMarkParser;
 import android.perftests.utils.TraceMarkParser.TraceMarkSlice;
-import android.support.test.uiautomator.UiDevice;
 import android.util.DataUnit;
 
 import androidx.benchmark.macro.MacrobenchmarkScope;
 import androidx.test.filters.LargeTest;
 import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.UiDevice;
 
 import com.android.utils.blob.FakeBlobData;
 
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java b/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java
index f56e1ee..36174c6 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java
@@ -20,6 +20,7 @@
 import android.app.JobSchedulerImpl;
 import android.app.SystemServiceRegistry;
 import android.app.tare.EconomyManager;
+import android.app.tare.IEconomyManager;
 import android.content.Context;
 import android.os.DeviceIdleManager;
 import android.os.IDeviceIdleController;
@@ -58,6 +59,7 @@
                 Context.POWER_EXEMPTION_SERVICE, PowerExemptionManager.class,
                 PowerExemptionManager::new);
         SystemServiceRegistry.registerStaticService(
-                Context.RESOURCE_ECONOMY_SERVICE, EconomyManager.class, EconomyManager::new);
+                Context.RESOURCE_ECONOMY_SERVICE, EconomyManager.class,
+                (b) -> new EconomyManager(IEconomyManager.Stub.asInterface(b)));
     }
 }
diff --git a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
index 581ea7a..0bea028 100644
--- a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
+++ b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
@@ -19,7 +19,9 @@
 import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.content.Context;
+import android.os.RemoteException;
 import android.util.Log;
 
 import java.lang.annotation.Retention;
@@ -30,6 +32,7 @@
  *
  * @hide
  */
+@TestApi
 @SystemService(Context.RESOURCE_ECONOMY_SERVICE)
 public class EconomyManager {
     private static final String TAG = "TARE-" + EconomyManager.class.getSimpleName();
@@ -95,13 +98,17 @@
         }
     }
 
-
+    /** @hide */
+    @TestApi
     public static final int ENABLED_MODE_OFF = 0;
+    /** @hide */
     public static final int ENABLED_MODE_ON = 1;
     /**
      * Go through the motions, tracking events, updating balances and other TARE state values,
      * but don't use TARE to affect actual device behavior.
+     * @hide
      */
+    @TestApi
     public static final int ENABLED_MODE_SHADOW = 2;
 
     /** @hide */
@@ -114,6 +121,7 @@
     public @interface EnabledMode {
     }
 
+    /** @hide */
     public static String enabledModeToString(@EnabledMode int mode) {
         switch (mode) {
             case ENABLED_MODE_OFF: return "ENABLED_MODE_OFF";
@@ -123,11 +131,18 @@
         }
     }
 
+    /** @hide */
+    @TestApi
     public static final String KEY_ENABLE_TARE_MODE = "enable_tare_mode";
+    /** @hide */
     public static final String KEY_ENABLE_POLICY_ALARM = "enable_policy_alarm";
+    /** @hide */
     public static final String KEY_ENABLE_POLICY_JOB_SCHEDULER = "enable_policy_job";
+    /** @hide */
     public static final int DEFAULT_ENABLE_TARE_MODE = ENABLED_MODE_OFF;
+    /** @hide */
     public static final boolean DEFAULT_ENABLE_POLICY_ALARM = true;
+    /** @hide */
     public static final boolean DEFAULT_ENABLE_POLICY_JOB_SCHEDULER = true;
 
     // Keys for AlarmManager TARE factors
@@ -612,4 +627,27 @@
     public static final long DEFAULT_JS_ACTION_JOB_MIN_RUNNING_BASE_PRICE_CAKES = arcToCake(1);
     /** @hide */
     public static final long DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE_CAKES = arcToCake(60);
+
+    //////// APIs below ////////
+
+    private final IEconomyManager mService;
+
+    /** @hide */
+    public EconomyManager(IEconomyManager service) {
+        mService = service;
+    }
+
+    /**
+     * Returns the current enabled status of TARE.
+     * @hide
+     */
+    @EnabledMode
+    @TestApi
+    public int getEnabledMode() {
+        try {
+            return mService.getEnabledMode();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/apex/jobscheduler/framework/java/android/app/tare/IEconomyManager.aidl b/apex/jobscheduler/framework/java/android/app/tare/IEconomyManager.aidl
index bb15011..2be0db7 100644
--- a/apex/jobscheduler/framework/java/android/app/tare/IEconomyManager.aidl
+++ b/apex/jobscheduler/framework/java/android/app/tare/IEconomyManager.aidl
@@ -21,4 +21,5 @@
   * {@hide}
   */
 interface IEconomyManager {
+    int getEnabledMode();
 }
diff --git a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
index 64b2423..fd8ddbc 100644
--- a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
@@ -69,8 +69,8 @@
      * @return {@code true} if the given notification channel is associated with any user-initiated
      * jobs.
      */
-    boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs(String notificationChannel,
-            int userId, String packageName);
+    boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs(
+            @NonNull String notificationChannel, int userId, @NonNull String packageName);
 
     /**
      * Report a snapshot of sync-related jobs back to the sync manager
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 0650ce3..f779b4d 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -355,7 +355,7 @@
     @GuardedBy("this")
     private boolean mHasGps;
     @GuardedBy("this")
-    private boolean mHasNetworkLocation;
+    private boolean mHasFusedLocation;
     @GuardedBy("this")
     private Location mLastGenericLocation;
     @GuardedBy("this")
@@ -3782,12 +3782,14 @@
                 scheduleAlarmLocked(mConstants.LOCATING_TIMEOUT);
                 LocationManager locationManager = mInjector.getLocationManager();
                 if (locationManager != null
-                        && locationManager.getProvider(LocationManager.NETWORK_PROVIDER) != null) {
-                    locationManager.requestLocationUpdates(mLocationRequest,
-                            mGenericLocationListener, mHandler.getLooper());
+                        && locationManager.getProvider(LocationManager.FUSED_PROVIDER) != null) {
+                    locationManager.requestLocationUpdates(LocationManager.FUSED_PROVIDER,
+                            mLocationRequest,
+                            AppSchedulingModuleThread.getExecutor(),
+                            mGenericLocationListener);
                     mLocating = true;
                 } else {
-                    mHasNetworkLocation = false;
+                    mHasFusedLocation = false;
                 }
                 if (locationManager != null
                         && locationManager.getProvider(LocationManager.GPS_PROVIDER) != null) {
@@ -5301,9 +5303,10 @@
                 pw.print("  "); pw.print(mStationaryListeners.size());
                 pw.println(" stationary listeners registered");
             }
-            pw.print("  mLocating="); pw.print(mLocating); pw.print(" mHasGps=");
-                    pw.print(mHasGps); pw.print(" mHasNetwork=");
-                    pw.print(mHasNetworkLocation); pw.print(" mLocated="); pw.println(mLocated);
+            pw.print("  mLocating="); pw.print(mLocating);
+            pw.print(" mHasGps="); pw.print(mHasGps);
+            pw.print(" mHasFused="); pw.print(mHasFusedLocation);
+            pw.print(" mLocated="); pw.println(mLocated);
             if (mLastGenericLocation != null) {
                 pw.print("  mLastGenericLocation="); pw.println(mLastGenericLocation);
             }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 8bd3d127..b9b825c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -1925,16 +1925,14 @@
         return null;
     }
 
-    @GuardedBy("mLock")
     boolean isNotificationAssociatedWithAnyUserInitiatedJobs(int notificationId, int userId,
-            String packageName) {
+            @NonNull String packageName) {
         return mNotificationCoordinator.isNotificationAssociatedWithAnyUserInitiatedJobs(
                 notificationId, userId, packageName);
     }
 
-    @GuardedBy("mLock")
-    boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs(String notificationChannel,
-            int userId, String packageName) {
+    boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs(
+            @NonNull String notificationChannel, int userId, @NonNull String packageName) {
         return mNotificationCoordinator.isNotificationChannelAssociatedWithAnyUserInitiatedJobs(
                 notificationChannel, userId, packageName);
     }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java b/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java
index f6e00ec..d94674b 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java
@@ -27,9 +27,12 @@
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.IntArray;
 import android.util.Slog;
+import android.util.SparseArrayMap;
 import android.util.SparseSetArray;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.server.LocalServices;
 import com.android.server.job.controllers.JobStatus;
 import com.android.server.notification.NotificationManagerInternal;
@@ -38,6 +41,14 @@
     private static final String TAG = "JobNotificationCoordinator";
 
     /**
+     * Local lock for independent objects like mUijNotifications and mUijNotificationChannels which
+     * don't depend on other JS objects such as JobServiceContext which require the global JS lock.
+     *
+     * Note: do <b>NOT</b> acquire the global lock while this one is held.
+     */
+    private final Object mUijLock = new Object();
+
+    /**
      * Mapping of UserPackage -> {notificationId -> List<JobServiceContext>} to track which jobs
      * are associated with each app's notifications.
      */
@@ -49,6 +60,27 @@
     private final ArrayMap<JobServiceContext, NotificationDetails> mNotificationDetails =
             new ArrayMap<>();
 
+    /**
+     * Mapping of userId -> {packageName, notificationIds} tracking which notifications
+     * associated with each app belong to user-initiated jobs.
+     *
+     * Note: this map can be accessed without holding the main JS lock, so that other services like
+     * NotificationManagerService can call into JS and verify associations.
+     */
+    @GuardedBy("mUijLock")
+    private final SparseArrayMap<String, IntArray> mUijNotifications = new SparseArrayMap<>();
+
+    /**
+     * Mapping of userId -> {packageName, notificationChannels} tracking which notification channels
+     * associated with each app are hosting a user-initiated job notification.
+     *
+     * Note: this map can be accessed without holding the main JS lock, so that other services like
+     * NotificationManagerService can call into JS and verify associations.
+     */
+    @GuardedBy("mUijLock")
+    private final SparseArrayMap<String, ArraySet<String>> mUijNotificationChannels =
+            new SparseArrayMap<>();
+
     private static final class NotificationDetails {
         @NonNull
         public final UserPackage userPackage;
@@ -81,15 +113,24 @@
             int callingPid, int callingUid, int notificationId, @NonNull Notification notification,
             @JobService.JobEndNotificationPolicy int jobEndNotificationPolicy) {
         validateNotification(packageName, callingUid, notification, jobEndNotificationPolicy);
+        final JobStatus jobStatus = hostingContext.getRunningJobLocked();
         final NotificationDetails oldDetails = mNotificationDetails.get(hostingContext);
         if (oldDetails != null && oldDetails.notificationId != notificationId) {
             // App is switching notification IDs. Remove association with the old one.
-            removeNotificationAssociation(hostingContext, JobParameters.STOP_REASON_UNDEFINED);
+            removeNotificationAssociation(hostingContext, JobParameters.STOP_REASON_UNDEFINED,
+                    jobStatus);
         }
         final int userId = UserHandle.getUserId(callingUid);
-        final JobStatus jobStatus = hostingContext.getRunningJobLocked();
         if (jobStatus != null && jobStatus.startedAsUserInitiatedJob) {
             notification.flags |= Notification.FLAG_USER_INITIATED_JOB;
+            synchronized (mUijLock) {
+                maybeCreateUijNotificationSetsLocked(userId, packageName);
+                final IntArray notificationIds = mUijNotifications.get(userId, packageName);
+                if (notificationIds.indexOf(notificationId) == -1) {
+                    notificationIds.add(notificationId);
+                }
+                mUijNotificationChannels.get(userId, packageName).add(notification.getChannelId());
+            }
         }
         final UserPackage userPackage = UserPackage.of(userId, packageName);
         final NotificationDetails details = new NotificationDetails(
@@ -110,7 +151,7 @@
     }
 
     void removeNotificationAssociation(@NonNull JobServiceContext hostingContext,
-            @JobParameters.StopReason int stopReason) {
+            @JobParameters.StopReason int stopReason, JobStatus completedJob) {
         final NotificationDetails details = mNotificationDetails.remove(hostingContext);
         if (details == null) {
             return;
@@ -121,10 +162,11 @@
             Slog.wtf(TAG, "Association data structures not in sync");
             return;
         }
-        final String packageName = details.userPackage.packageName;
         final int userId = UserHandle.getUserId(details.appUid);
+        final String packageName = details.userPackage.packageName;
+        final int notificationId = details.notificationId;
         boolean stripUijFlag = true;
-        ArraySet<JobServiceContext> associatedContexts = associations.get(details.notificationId);
+        ArraySet<JobServiceContext> associatedContexts = associations.get(notificationId);
         if (associatedContexts == null || associatedContexts.isEmpty()) {
             // No more jobs using this notification. Apply the final job stop policy.
             // If the user attempted to stop the job/app, then always remove the notification
@@ -133,23 +175,50 @@
                     || stopReason == JobParameters.STOP_REASON_USER) {
                 mNotificationManagerInternal.cancelNotification(
                         packageName, packageName, details.appUid, details.appPid, /* tag */ null,
-                        details.notificationId, userId);
+                        notificationId, userId);
                 stripUijFlag = false;
             }
         } else {
             // Strip the UIJ flag only if there are no other UIJs associated with the notification
-            stripUijFlag = !isNotificationAssociatedWithAnyUserInitiatedJobs(
-                    details.notificationId, userId, packageName);
+            stripUijFlag = !isNotificationUsedForAnyUij(userId, packageName, notificationId);
         }
         if (stripUijFlag) {
-            // Strip the user-initiated job flag from the notification.
             mNotificationManagerInternal.removeUserInitiatedJobFlagFromNotification(
-                    packageName, details.notificationId, userId);
+                    packageName, notificationId, userId);
+        }
+
+        // Clean up UIJ related objects if the just completed job was a UIJ
+        if (completedJob != null && completedJob.startedAsUserInitiatedJob) {
+            maybeDeleteNotificationIdAssociation(userId, packageName, notificationId);
+            maybeDeleteNotificationChannelAssociation(
+                    userId, packageName, details.notificationChannel);
         }
     }
 
     boolean isNotificationAssociatedWithAnyUserInitiatedJobs(int notificationId,
-            int userId, String packageName) {
+            int userId, @NonNull String packageName) {
+        synchronized (mUijLock) {
+            final IntArray notifications = mUijNotifications.get(userId, packageName);
+            if (notifications != null) {
+                return notifications.indexOf(notificationId) != -1;
+            }
+            return false;
+        }
+    }
+
+    boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs(
+            @NonNull String notificationChannel, int userId, @NonNull String packageName) {
+        synchronized (mUijLock) {
+            final ArraySet<String> channels = mUijNotificationChannels.get(userId, packageName);
+            if (channels != null) {
+                return channels.contains(notificationChannel);
+            }
+            return false;
+        }
+    }
+
+    private boolean isNotificationUsedForAnyUij(int userId, String packageName,
+            int notificationId) {
         final UserPackage pkgDetails = UserPackage.of(userId, packageName);
         final SparseSetArray<JobServiceContext> associations = mCurrentAssociations.get(pkgDetails);
         if (associations == null) {
@@ -170,8 +239,26 @@
         return false;
     }
 
-    boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs(String notificationChannel,
-            int userId, String packageName) {
+    private void maybeDeleteNotificationIdAssociation(int userId, String packageName,
+            int notificationId) {
+        if (isNotificationUsedForAnyUij(userId, packageName, notificationId)) {
+            return;
+        }
+
+        // Safe to delete - no UIJs for this package are using this notification id
+        synchronized (mUijLock) {
+            final IntArray notifications = mUijNotifications.get(userId, packageName);
+            if (notifications != null) {
+                notifications.remove(notifications.indexOf(notificationId));
+                if (notifications.size() == 0) {
+                    mUijNotifications.delete(userId, packageName);
+                }
+            }
+        }
+    }
+
+    private void maybeDeleteNotificationChannelAssociation(int userId, String packageName,
+            String notificationChannel) {
         for (int i = mNotificationDetails.size() - 1; i >= 0; i--) {
             final JobServiceContext jsc = mNotificationDetails.keyAt(i);
             final NotificationDetails details = mNotificationDetails.get(jsc);
@@ -183,11 +270,31 @@
                     && details.notificationChannel.equals(notificationChannel)) {
                 final JobStatus jobStatus = jsc.getRunningJobLocked();
                 if (jobStatus != null && jobStatus.startedAsUserInitiatedJob) {
-                    return true;
+                    return;
                 }
             }
         }
-        return false;
+
+        // Safe to delete - no UIJs for this package are using this notification channel
+        synchronized (mUijLock) {
+            ArraySet<String> channels = mUijNotificationChannels.get(userId, packageName);
+            if (channels != null) {
+                channels.remove(notificationChannel);
+                if (channels.isEmpty()) {
+                    mUijNotificationChannels.delete(userId, packageName);
+                }
+            }
+        }
+    }
+
+    @GuardedBy("mUijLock")
+    private void maybeCreateUijNotificationSetsLocked(int userId, String packageName) {
+        if (mUijNotifications.get(userId, packageName) == null) {
+            mUijNotifications.add(userId, packageName, new IntArray());
+        }
+        if (mUijNotificationChannels.get(userId, packageName) == null) {
+            mUijNotificationChannels.add(userId, packageName, new ArraySet<>());
+        }
     }
 
     private void validateNotification(@NonNull String packageName, int callingUid,
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index aef9dd0..6eeff82 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1588,12 +1588,12 @@
         final ArrayMap<String, List<JobInfo>> outMap = new ArrayMap<>();
         synchronized (mLock) {
             ArraySet<JobStatus> jobs = mJobs.getJobsByUid(uid);
-            // Write out for loop to avoid addAll() creating an Iterator.
+            // Write out for loop to avoid creating an Iterator.
             for (int i = jobs.size() - 1; i >= 0; i--) {
                 final JobStatus job = jobs.valueAt(i);
                 List<JobInfo> outList = outMap.get(job.getNamespace());
                 if (outList == null) {
-                    outList = new ArrayList<JobInfo>(jobs.size());
+                    outList = new ArrayList<>();
                     outMap.put(job.getNamespace(), outList);
                 }
 
@@ -1606,7 +1606,7 @@
     private List<JobInfo> getPendingJobsInNamespace(int uid, @Nullable String namespace) {
         synchronized (mLock) {
             ArraySet<JobStatus> jobs = mJobs.getJobsByUid(uid);
-            ArrayList<JobInfo> outList = new ArrayList<JobInfo>(jobs.size());
+            ArrayList<JobInfo> outList = new ArrayList<>();
             // Write out for loop to avoid addAll() creating an Iterator.
             for (int i = jobs.size() - 1; i >= 0; i--) {
                 final JobStatus job = jobs.valueAt(i);
@@ -3713,26 +3713,22 @@
 
         @Override
         public boolean isNotificationAssociatedWithAnyUserInitiatedJobs(int notificationId,
-                int userId, String packageName) {
+                int userId, @NonNull String packageName) {
             if (packageName == null) {
                 return false;
             }
-            synchronized (mLock) {
-                return mConcurrencyManager.isNotificationAssociatedWithAnyUserInitiatedJobs(
-                        notificationId, userId, packageName);
-            }
+            return mConcurrencyManager.isNotificationAssociatedWithAnyUserInitiatedJobs(
+                    notificationId, userId, packageName);
         }
 
         @Override
         public boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs(
-                String notificationChannel, int userId, String packageName) {
+                @NonNull String notificationChannel, int userId, @NonNull String packageName) {
             if (packageName == null || notificationChannel == null) {
                 return false;
             }
-            synchronized (mLock) {
-                return mConcurrencyManager.isNotificationChannelAssociatedWithAnyUserInitiatedJobs(
-                        notificationChannel, userId, packageName);
-            }
+            return mConcurrencyManager.isNotificationChannelAssociatedWithAnyUserInitiatedJobs(
+                    notificationChannel, userId, packageName);
         }
 
         @Override
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 44700c8..fb36cde 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -1464,7 +1464,8 @@
                     JobSchedulerEconomicPolicy.ACTION_JOB_TIMEOUT,
                     String.valueOf(mRunningJob.getJobId()));
         }
-        mNotificationCoordinator.removeNotificationAssociation(this, reschedulingStopReason);
+        mNotificationCoordinator.removeNotificationAssociation(this,
+                reschedulingStopReason, completedJob);
         if (mWakeLock != null) {
             mWakeLock.release();
         }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
index fc60228..ba62e96 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
@@ -31,7 +31,7 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.expresslog.Counter;
+import com.android.modules.expresslog.Counter;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateControllerProto;
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
index eb43c38..ef634b5 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
@@ -92,13 +92,17 @@
         final int priority = job.getEffectivePriority();
         if (mThermalStatus >= HIGHER_PRIORITY_THRESHOLD) {
             // For moderate throttling:
-            // Only let expedited & user-initiated jobs run if:
+            // Let all user-initiated jobs run.
+            // Only let expedited jobs run if:
             // 1. They haven't previously run
             // 2. They're already running and aren't yet in overtime
             // Only let high priority jobs run if:
             //   They are already running and aren't yet in overtime
             // Don't let any other job run.
-            if (job.shouldTreatAsExpeditedJob() || job.shouldTreatAsUserInitiatedJob()) {
+            if (job.shouldTreatAsUserInitiatedJob()) {
+                return false;
+            }
+            if (job.shouldTreatAsExpeditedJob()) {
                 return job.getNumPreviousAttempts() > 0
                         || (mService.isCurrentlyRunningLocked(job)
                                 && mService.isJobInOvertimeLocked(job));
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index 7f6a75e..c707069 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -1351,6 +1351,11 @@
         }
 
         @Override
+        public int getEnabledMode() {
+            return InternalResourceService.this.getEnabledMode();
+        }
+
+        @Override
         public int handleShellCommand(@NonNull ParcelFileDescriptor in,
                 @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
                 @NonNull String[] args) {
diff --git a/boot/boot-image-profile.txt b/boot/boot-image-profile.txt
index 996c388..aebace5 100644
--- a/boot/boot-image-profile.txt
+++ b/boot/boot-image-profile.txt
@@ -33583,8 +33583,8 @@
 Lcom/android/internal/dynamicanimation/animation/Force;
 Lcom/android/internal/dynamicanimation/animation/SpringAnimation;
 Lcom/android/internal/dynamicanimation/animation/SpringForce;
-Lcom/android/internal/expresslog/Counter;
-Lcom/android/internal/expresslog/Utils;
+Lcom/android/modules/expresslog/Counter;
+Lcom/android/modules/expresslog/Utils;
 Lcom/android/internal/graphics/ColorUtils$ContrastCalculator;
 Lcom/android/internal/graphics/ColorUtils;
 Lcom/android/internal/graphics/SfVsyncFrameCallbackProvider;
diff --git a/boot/preloaded-classes b/boot/preloaded-classes
index 21ae134..4293caf 100644
--- a/boot/preloaded-classes
+++ b/boot/preloaded-classes
@@ -10784,8 +10784,8 @@
 com.android.internal.dynamicanimation.animation.Force
 com.android.internal.dynamicanimation.animation.SpringAnimation
 com.android.internal.dynamicanimation.animation.SpringForce
-com.android.internal.expresslog.Counter
-com.android.internal.expresslog.Utils
+com.android.modules.expresslog.Counter
+com.android.modules.expresslog.Utils
 com.android.internal.graphics.ColorUtils$ContrastCalculator
 com.android.internal.graphics.ColorUtils
 com.android.internal.graphics.SfVsyncFrameCallbackProvider
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index 6998081..b6dc32a 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -21,6 +21,7 @@
 import android.app.backup.BackupManager;
 import android.app.backup.BackupManagerMonitor;
 import android.app.backup.BackupProgress;
+import android.app.backup.BackupRestoreEventLogger;
 import android.app.backup.BackupTransport;
 import android.app.backup.IBackupManager;
 import android.app.backup.IBackupManagerMonitor;
@@ -821,14 +822,22 @@
             doRestorePackage(arg);
         } else {
             try {
+                @Monitor int monitor = Monitor.OFF;
+
                 long token = Long.parseLong(arg, 16);
                 HashSet<String> filter = null;
                 while ((arg = nextArg()) != null) {
-                    if (filter == null) filter = new HashSet<String>();
-                    filter.add(arg);
+                    if (arg.equals("--monitor")) {
+                        monitor = Monitor.NORMAL;
+                    } else if (arg.equals("--monitor-verbose")) {
+                        monitor = Monitor.VERBOSE;
+                    } else {
+                        if (filter == null) filter = new HashSet<String>();
+                        filter.add(arg);
+                    }
                 }
 
-                doRestoreAll(userId, token, filter);
+                doRestoreAll(userId, token, filter, monitor);
             } catch (NumberFormatException e) {
                 showUsage();
                 return;
@@ -841,7 +850,8 @@
         System.err.println("'restore <token> <package>'.");
     }
 
-    private void doRestoreAll(@UserIdInt int userId, long token, HashSet<String> filter) {
+    private void doRestoreAll(@UserIdInt int userId, long token, HashSet<String> filter,
+            @Monitor int monitorState) {
         RestoreObserver observer = new RestoreObserver();
 
         try {
@@ -852,8 +862,11 @@
                 return;
             }
             RestoreSet[] sets = null;
-            // TODO implement monitor here
-            int err = mRestore.getAvailableRestoreSets(observer, null);
+            BackupMonitor monitor =
+                    (monitorState != Monitor.OFF)
+                            ? new BackupMonitor(monitorState == Monitor.VERBOSE)
+                            : null;
+            int err = mRestore.getAvailableRestoreSets(observer, monitor);
             if (err == 0) {
                 observer.waitForCompletion();
                 sets = observer.sets;
@@ -862,12 +875,12 @@
                         if (s.token == token) {
                             System.out.println("Scheduling restore: " + s.name);
                             if (filter == null) {
-                                didRestore = (mRestore.restoreAll(token, observer, null) == 0);
+                                didRestore = (mRestore.restoreAll(token, observer, monitor) == 0);
                             } else {
                                 String[] names = new String[filter.size()];
                                 filter.toArray(names);
                                 didRestore = (mRestore.restorePackages(token, observer, names,
-                                        null) == 0);
+                                        monitor) == 0);
                             }
                             break;
                         }
@@ -958,8 +971,8 @@
         System.err.println("       bmgr list transports [-c]");
         System.err.println("       bmgr list sets");
         System.err.println("       bmgr transport WHICH|-c WHICH_COMPONENT");
-        System.err.println("       bmgr restore TOKEN");
-        System.err.println("       bmgr restore TOKEN PACKAGE...");
+        System.err.println("       bmgr restore TOKEN [--monitor|--monitor-verbose]");
+        System.err.println("       bmgr restore TOKEN PACKAGE... [--monitor|--monitor-verbose]");
         System.err.println("       bmgr run");
         System.err.println("       bmgr wipe TRANSPORT PACKAGE");
         System.err.println("       bmgr fullbackup PACKAGE...");
@@ -1005,12 +1018,18 @@
         System.err.println("restore operation from the currently active transport.  It will deliver");
         System.err.println("the restore set designated by the TOKEN argument to each application");
         System.err.println("that had contributed data to that restore set.");
+        System.err.println("    --monitor flag prints monitor events (important events and errors");
+        System.err.println("              encountered during restore).");
+        System.err.println("    --monitor-verbose flag prints monitor events with all keys.");
         System.err.println("");
         System.err.println("The 'restore' command when given a token and one or more package names");
         System.err.println("initiates a restore operation of just those given packages from the restore");
         System.err.println("set designated by the TOKEN argument.  It is effectively the same as the");
         System.err.println("'restore' operation supplying only a token, but applies a filter to the");
         System.err.println("set of applications to be restored.");
+        System.err.println("    --monitor flag prints monitor events (important events and errors");
+        System.err.println("              encountered during restore).");
+        System.err.println("    --monitor-verbose flag prints monitor events with all keys.");
         System.err.println("");
         System.err.println("The 'run' command causes any scheduled backup operation to be initiated");
         System.err.println("immediately, without the usual waiting period for batching together");
@@ -1026,7 +1045,8 @@
         System.err.println("");
         System.err.println("The 'backupnow' command runs an immediate backup for one or more packages.");
         System.err.println("    --all flag runs backup for all eligible packages.");
-        System.err.println("    --monitor flag prints monitor events.");
+        System.err.println("    --monitor flag prints monitor events (important events and errors");
+        System.err.println("              encountered during backup).");
         System.err.println("    --monitor-verbose flag prints monitor events with all keys.");
         System.err.println("For each package it will run key/value or full data backup ");
         System.err.println("depending on the package's manifest declarations.");
@@ -1076,6 +1096,37 @@
                     out.append("(v").append(version).append(")");
                 }
             }
+            if (event.containsKey(BackupManagerMonitor.EXTRA_LOG_AGENT_LOGGING_RESULTS)) {
+                ArrayList<BackupRestoreEventLogger.DataTypeResult> results =
+                        event.getParcelableArrayList(
+                                BackupManagerMonitor.EXTRA_LOG_AGENT_LOGGING_RESULTS,
+                                BackupRestoreEventLogger.DataTypeResult.class);
+                out.append(", results = [");
+                for (BackupRestoreEventLogger.DataTypeResult result : results) {
+                    out.append("\n{\n\tdataType: ");
+                    out.append(result.getDataType());
+                    out.append("\n\tsuccessCount: ");
+                    out.append(result.getSuccessCount());
+                    out.append("\n\tfailCount: ");
+                    out.append(result.getFailCount());
+                    out.append("\n\tmetadataHash: ");
+                    out.append(Arrays.toString(result.getMetadataHash()));
+
+                    if (!result.getErrors().isEmpty()) {
+                        out.append("\n\terrors: [");
+                        for (String error : result.getErrors().keySet()) {
+                            out.append(error);
+                            out.append(": ");
+                            out.append(result.getErrors().get(error));
+                            out.append(";");
+                        }
+                        out.append("]");
+                    }
+                    out.append("\n}");
+
+                }
+                out.append("]");
+            }
             if (mVerbose) {
                 Set<String> remainingKeys = new ArraySet<>(event.keySet());
                 remainingKeys.remove(BackupManagerMonitor.EXTRA_LOG_EVENT_ID);
@@ -1083,6 +1134,7 @@
                 remainingKeys.remove(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_NAME);
                 remainingKeys.remove(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_LONG_VERSION);
                 remainingKeys.remove(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_VERSION);
+                remainingKeys.remove(BackupManagerMonitor.EXTRA_LOG_AGENT_LOGGING_RESULTS);
                 if (!remainingKeys.isEmpty()) {
                     out.append(", other keys =");
                     for (String key : remainingKeys) {
@@ -1192,6 +1244,8 @@
                 return "NO_PACKAGES";
             case BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_IS_NULL:
                 return "TRANSPORT_IS_NULL";
+            case BackupManagerMonitor.LOG_EVENT_ID_AGENT_LOGGING_RESULTS:
+                return "AGENT_LOGGING_RESULTS";
             default:
                 return "UNKNOWN_ID";
         }
diff --git a/config/boot-image-profile.txt b/config/boot-image-profile.txt
index 3cc9908..bb07487 100644
--- a/config/boot-image-profile.txt
+++ b/config/boot-image-profile.txt
@@ -43717,8 +43717,8 @@
 Lcom/android/internal/dynamicanimation/animation/Force;
 Lcom/android/internal/dynamicanimation/animation/SpringAnimation;
 Lcom/android/internal/dynamicanimation/animation/SpringForce;
-Lcom/android/internal/expresslog/Counter;
-Lcom/android/internal/expresslog/Utils;
+Lcom/android/modules/expresslog/Counter;
+Lcom/android/modules/expresslog/Utils;
 Lcom/android/internal/graphics/ColorUtils$ContrastCalculator;
 Lcom/android/internal/graphics/ColorUtils;
 Lcom/android/internal/graphics/SfVsyncFrameCallbackProvider;
diff --git a/config/preloaded-classes b/config/preloaded-classes
index 8e50fe8..1812c2b 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -10815,8 +10815,8 @@
 com.android.internal.dynamicanimation.animation.Force
 com.android.internal.dynamicanimation.animation.SpringAnimation
 com.android.internal.dynamicanimation.animation.SpringForce
-com.android.internal.expresslog.Counter
-com.android.internal.expresslog.Utils
+com.android.modules.expresslog.Counter
+com.android.modules.expresslog.Utils
 com.android.internal.graphics.ColorUtils$ContrastCalculator
 com.android.internal.graphics.ColorUtils
 com.android.internal.graphics.SfVsyncFrameCallbackProvider
diff --git a/core/api/current.txt b/core/api/current.txt
index e10fbf2..288ab47 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -377,7 +377,7 @@
   public static final class R.attr {
     ctor public R.attr();
     field public static final int absListViewStyle = 16842858; // 0x101006a
-    field public static final int accessibilityDataSensitive;
+    field public static final int accessibilityDataSensitive = 16844407; // 0x1010677
     field public static final int accessibilityEventTypes = 16843648; // 0x1010380
     field public static final int accessibilityFeedbackType = 16843650; // 0x1010382
     field public static final int accessibilityFlags = 16843652; // 0x1010384
@@ -445,12 +445,12 @@
     field public static final int allowGameFpsOverride = 16844378; // 0x101065a
     field public static final int allowNativeHeapPointerTagging = 16844306; // 0x1010612
     field public static final int allowParallelSyncs = 16843570; // 0x1010332
-    field public static final int allowSharedIsolatedProcess;
+    field public static final int allowSharedIsolatedProcess = 16844413; // 0x101067d
     field public static final int allowSingleTap = 16843353; // 0x1010259
     field public static final int allowTaskReparenting = 16843268; // 0x1010204
     field public static final int allowUndo = 16843999; // 0x10104df
     field public static final int allowUntrustedActivityEmbedding = 16844393; // 0x1010669
-    field public static final int allowUpdateOwnership;
+    field public static final int allowUpdateOwnership = 16844416; // 0x1010680
     field public static final int alpha = 16843551; // 0x101031f
     field public static final int alphabeticModifiers = 16844110; // 0x101054e
     field public static final int alphabeticShortcut = 16843235; // 0x10101e3
@@ -556,7 +556,7 @@
     field public static final int canTakeScreenshot = 16844303; // 0x101060f
     field public static final int candidatesTextStyleSpans = 16843312; // 0x1010230
     field public static final int cantSaveState = 16844142; // 0x101056e
-    field public static final int capability;
+    field public static final int capability = 16844423; // 0x1010687
     field @Deprecated public static final int capitalize = 16843113; // 0x1010169
     field public static final int category = 16843752; // 0x10103e8
     field public static final int centerBright = 16842956; // 0x10100cc
@@ -741,7 +741,7 @@
     field public static final int ellipsize = 16842923; // 0x10100ab
     field public static final int ems = 16843096; // 0x1010158
     field public static final int enableOnBackInvokedCallback = 16844396; // 0x101066c
-    field public static final int enableTextStylingShortcuts;
+    field public static final int enableTextStylingShortcuts = 16844408; // 0x1010678
     field public static final int enableVrMode = 16844069; // 0x1010525
     field public static final int enabled = 16842766; // 0x101000e
     field public static final int end = 16843996; // 0x10104dc
@@ -810,7 +810,7 @@
     field public static final int focusableInTouchMode = 16842971; // 0x10100db
     field public static final int focusedByDefault = 16844100; // 0x1010544
     field @Deprecated public static final int focusedMonthDateColor = 16843587; // 0x1010343
-    field public static final int focusedSearchResultHighlightColor;
+    field public static final int focusedSearchResultHighlightColor = 16844419; // 0x1010683
     field public static final int font = 16844082; // 0x1010532
     field public static final int fontFamily = 16843692; // 0x10103ac
     field public static final int fontFeatureSettings = 16843959; // 0x10104b7
@@ -896,10 +896,10 @@
     field public static final int hand_secondTintMode = 16844349; // 0x101063d
     field public static final int handle = 16843354; // 0x101025a
     field public static final int handleProfiling = 16842786; // 0x1010022
-    field public static final int handwritingBoundsOffsetBottom;
-    field public static final int handwritingBoundsOffsetLeft;
-    field public static final int handwritingBoundsOffsetRight;
-    field public static final int handwritingBoundsOffsetTop;
+    field public static final int handwritingBoundsOffsetBottom = 16844406; // 0x1010676
+    field public static final int handwritingBoundsOffsetLeft = 16844403; // 0x1010673
+    field public static final int handwritingBoundsOffsetRight = 16844405; // 0x1010675
+    field public static final int handwritingBoundsOffsetTop = 16844404; // 0x1010674
     field public static final int hapticFeedbackEnabled = 16843358; // 0x101025e
     field public static final int hardwareAccelerated = 16843475; // 0x10102d3
     field public static final int hasCode = 16842764; // 0x101000c
@@ -986,7 +986,7 @@
     field public static final int isAlwaysSyncable = 16843571; // 0x1010333
     field public static final int isAsciiCapable = 16843753; // 0x10103e9
     field public static final int isAuxiliary = 16843647; // 0x101037f
-    field public static final int isCredential;
+    field public static final int isCredential = 16844417; // 0x1010681
     field public static final int isDefault = 16843297; // 0x1010221
     field public static final int isFeatureSplit = 16844123; // 0x101055b
     field public static final int isGame = 16843764; // 0x10103f4
@@ -1021,8 +1021,8 @@
     field @Deprecated public static final int keyTextSize = 16843316; // 0x1010234
     field @Deprecated public static final int keyWidth = 16843325; // 0x101023d
     field public static final int keyboardLayout = 16843691; // 0x10103ab
-    field public static final int keyboardLayoutType;
-    field public static final int keyboardLocale;
+    field public static final int keyboardLayoutType = 16844415; // 0x101067f
+    field public static final int keyboardLocale = 16844414; // 0x101067e
     field @Deprecated public static final int keyboardMode = 16843341; // 0x101024d
     field public static final int keyboardNavigationCluster = 16844096; // 0x1010540
     field public static final int keycode = 16842949; // 0x10100c5
@@ -1263,8 +1263,8 @@
     field public static final int persistentDrawingCache = 16842990; // 0x10100ee
     field public static final int persistentWhenFeatureAvailable = 16844131; // 0x1010563
     field @Deprecated public static final int phoneNumber = 16843111; // 0x1010167
-    field public static final int physicalKeyboardHintLanguageTag;
-    field public static final int physicalKeyboardHintLayoutType;
+    field public static final int physicalKeyboardHintLanguageTag = 16844411; // 0x101067b
+    field public static final int physicalKeyboardHintLayoutType = 16844412; // 0x101067c
     field public static final int pivotX = 16843189; // 0x10101b5
     field public static final int pivotY = 16843190; // 0x10101b6
     field public static final int pointerIcon = 16844041; // 0x1010509
@@ -1354,7 +1354,7 @@
     field public static final int requireDeviceUnlock = 16843756; // 0x10103ec
     field public static final int required = 16843406; // 0x101028e
     field public static final int requiredAccountType = 16843734; // 0x10103d6
-    field public static final int requiredDisplayCategory;
+    field public static final int requiredDisplayCategory = 16844409; // 0x1010679
     field public static final int requiredFeature = 16844116; // 0x1010554
     field public static final int requiredForAllUsers = 16843728; // 0x10103d0
     field public static final int requiredNotFeature = 16844117; // 0x1010555
@@ -1422,7 +1422,7 @@
     field public static final int searchHintIcon = 16843988; // 0x10104d4
     field public static final int searchIcon = 16843907; // 0x1010483
     field public static final int searchMode = 16843221; // 0x10101d5
-    field public static final int searchResultHighlightColor;
+    field public static final int searchResultHighlightColor = 16844418; // 0x1010682
     field public static final int searchSettingsDescription = 16843402; // 0x101028a
     field public static final int searchSuggestAuthority = 16843222; // 0x10101d6
     field public static final int searchSuggestIntentAction = 16843225; // 0x10101d9
@@ -1449,7 +1449,7 @@
     field public static final int sessionService = 16843837; // 0x101043d
     field public static final int settingsActivity = 16843301; // 0x1010225
     field public static final int settingsSliceUri = 16844179; // 0x1010593
-    field public static final int settingsSubtitle;
+    field public static final int settingsSubtitle = 16844422; // 0x1010686
     field public static final int setupActivity = 16843766; // 0x10103f6
     field public static final int shadowColor = 16843105; // 0x1010161
     field public static final int shadowDx = 16843106; // 0x1010162
@@ -1555,7 +1555,7 @@
     field public static final int strokeLineJoin = 16843788; // 0x101040c
     field public static final int strokeMiterLimit = 16843789; // 0x101040d
     field public static final int strokeWidth = 16843783; // 0x1010407
-    field public static final int stylusHandwritingSettingsActivity;
+    field public static final int stylusHandwritingSettingsActivity = 16844420; // 0x1010684
     field public static final int subMenuArrow = 16844019; // 0x10104f3
     field public static final int submitBackground = 16843912; // 0x1010488
     field public static final int subtitle = 16843473; // 0x10102d1
@@ -1861,7 +1861,7 @@
     field public static final int windowMinWidthMajor = 16843606; // 0x1010356
     field public static final int windowMinWidthMinor = 16843607; // 0x1010357
     field public static final int windowNoDisplay = 16843294; // 0x101021e
-    field public static final int windowNoMoveAnimation;
+    field public static final int windowNoMoveAnimation = 16844421; // 0x1010685
     field public static final int windowNoTitle = 16842838; // 0x1010056
     field @Deprecated public static final int windowOverscan = 16843727; // 0x10103cf
     field public static final int windowReenterTransition = 16843951; // 0x10104af
@@ -1966,18 +1966,18 @@
     field public static final int system_accent3_700 = 17170522; // 0x106005a
     field public static final int system_accent3_800 = 17170523; // 0x106005b
     field public static final int system_accent3_900 = 17170524; // 0x106005c
-    field public static final int system_background_dark;
-    field public static final int system_background_light;
-    field public static final int system_control_activated_dark;
-    field public static final int system_control_activated_light;
-    field public static final int system_control_highlight_dark;
-    field public static final int system_control_highlight_light;
-    field public static final int system_control_normal_dark;
-    field public static final int system_control_normal_light;
-    field public static final int system_error_container_dark;
-    field public static final int system_error_container_light;
-    field public static final int system_error_dark;
-    field public static final int system_error_light;
+    field public static final int system_background_dark = 17170581; // 0x1060095
+    field public static final int system_background_light = 17170538; // 0x106006a
+    field public static final int system_control_activated_dark = 17170599; // 0x10600a7
+    field public static final int system_control_activated_light = 17170556; // 0x106007c
+    field public static final int system_control_highlight_dark = 17170601; // 0x10600a9
+    field public static final int system_control_highlight_light = 17170558; // 0x106007e
+    field public static final int system_control_normal_dark = 17170600; // 0x10600a8
+    field public static final int system_control_normal_light = 17170557; // 0x106007d
+    field public static final int system_error_container_dark = 17170597; // 0x10600a5
+    field public static final int system_error_container_light = 17170554; // 0x106007a
+    field public static final int system_error_dark = 17170595; // 0x10600a3
+    field public static final int system_error_light = 17170552; // 0x1060078
     field public static final int system_neutral1_0 = 17170461; // 0x106001d
     field public static final int system_neutral1_10 = 17170462; // 0x106001e
     field public static final int system_neutral1_100 = 17170464; // 0x1060020
@@ -2004,94 +2004,94 @@
     field public static final int system_neutral2_700 = 17170483; // 0x1060033
     field public static final int system_neutral2_800 = 17170484; // 0x1060034
     field public static final int system_neutral2_900 = 17170485; // 0x1060035
-    field public static final int system_on_background_dark;
-    field public static final int system_on_background_light;
-    field public static final int system_on_error_container_dark;
-    field public static final int system_on_error_container_light;
-    field public static final int system_on_error_dark;
-    field public static final int system_on_error_light;
-    field public static final int system_on_primary_container_dark;
-    field public static final int system_on_primary_container_light;
-    field public static final int system_on_primary_dark;
-    field public static final int system_on_primary_fixed;
-    field public static final int system_on_primary_fixed_variant;
-    field public static final int system_on_primary_light;
-    field public static final int system_on_secondary_container_dark;
-    field public static final int system_on_secondary_container_light;
-    field public static final int system_on_secondary_dark;
-    field public static final int system_on_secondary_fixed;
-    field public static final int system_on_secondary_fixed_variant;
-    field public static final int system_on_secondary_light;
-    field public static final int system_on_surface_dark;
-    field public static final int system_on_surface_light;
-    field public static final int system_on_surface_variant_dark;
-    field public static final int system_on_surface_variant_light;
-    field public static final int system_on_tertiary_container_dark;
-    field public static final int system_on_tertiary_container_light;
-    field public static final int system_on_tertiary_dark;
-    field public static final int system_on_tertiary_fixed;
-    field public static final int system_on_tertiary_fixed_variant;
-    field public static final int system_on_tertiary_light;
-    field public static final int system_outline_dark;
-    field public static final int system_outline_light;
-    field public static final int system_outline_variant_dark;
-    field public static final int system_outline_variant_light;
-    field public static final int system_palette_key_color_neutral_dark;
-    field public static final int system_palette_key_color_neutral_light;
-    field public static final int system_palette_key_color_neutral_variant_dark;
-    field public static final int system_palette_key_color_neutral_variant_light;
-    field public static final int system_palette_key_color_primary_dark;
-    field public static final int system_palette_key_color_primary_light;
-    field public static final int system_palette_key_color_secondary_dark;
-    field public static final int system_palette_key_color_secondary_light;
-    field public static final int system_palette_key_color_tertiary_dark;
-    field public static final int system_palette_key_color_tertiary_light;
-    field public static final int system_primary_container_dark;
-    field public static final int system_primary_container_light;
-    field public static final int system_primary_dark;
-    field public static final int system_primary_fixed;
-    field public static final int system_primary_fixed_dim;
-    field public static final int system_primary_light;
-    field public static final int system_secondary_container_dark;
-    field public static final int system_secondary_container_light;
-    field public static final int system_secondary_dark;
-    field public static final int system_secondary_fixed;
-    field public static final int system_secondary_fixed_dim;
-    field public static final int system_secondary_light;
-    field public static final int system_surface_bright_dark;
-    field public static final int system_surface_bright_light;
-    field public static final int system_surface_container_dark;
-    field public static final int system_surface_container_high_dark;
-    field public static final int system_surface_container_high_light;
-    field public static final int system_surface_container_highest_dark;
-    field public static final int system_surface_container_highest_light;
-    field public static final int system_surface_container_light;
-    field public static final int system_surface_container_low_dark;
-    field public static final int system_surface_container_low_light;
-    field public static final int system_surface_container_lowest_dark;
-    field public static final int system_surface_container_lowest_light;
-    field public static final int system_surface_dark;
-    field public static final int system_surface_dim_dark;
-    field public static final int system_surface_dim_light;
-    field public static final int system_surface_light;
-    field public static final int system_surface_variant_dark;
-    field public static final int system_surface_variant_light;
-    field public static final int system_tertiary_container_dark;
-    field public static final int system_tertiary_container_light;
-    field public static final int system_tertiary_dark;
-    field public static final int system_tertiary_fixed;
-    field public static final int system_tertiary_fixed_dim;
-    field public static final int system_tertiary_light;
-    field public static final int system_text_hint_inverse_dark;
-    field public static final int system_text_hint_inverse_light;
-    field public static final int system_text_primary_inverse_dark;
-    field public static final int system_text_primary_inverse_disable_only_dark;
-    field public static final int system_text_primary_inverse_disable_only_light;
-    field public static final int system_text_primary_inverse_light;
-    field public static final int system_text_secondary_and_tertiary_inverse_dark;
-    field public static final int system_text_secondary_and_tertiary_inverse_disabled_dark;
-    field public static final int system_text_secondary_and_tertiary_inverse_disabled_light;
-    field public static final int system_text_secondary_and_tertiary_inverse_light;
+    field public static final int system_on_background_dark = 17170582; // 0x1060096
+    field public static final int system_on_background_light = 17170539; // 0x106006b
+    field public static final int system_on_error_container_dark = 17170598; // 0x10600a6
+    field public static final int system_on_error_container_light = 17170555; // 0x106007b
+    field public static final int system_on_error_dark = 17170596; // 0x10600a4
+    field public static final int system_on_error_light = 17170553; // 0x1060079
+    field public static final int system_on_primary_container_dark = 17170570; // 0x106008a
+    field public static final int system_on_primary_container_light = 17170527; // 0x106005f
+    field public static final int system_on_primary_dark = 17170572; // 0x106008c
+    field public static final int system_on_primary_fixed = 17170614; // 0x10600b6
+    field public static final int system_on_primary_fixed_variant = 17170615; // 0x10600b7
+    field public static final int system_on_primary_light = 17170529; // 0x1060061
+    field public static final int system_on_secondary_container_dark = 17170574; // 0x106008e
+    field public static final int system_on_secondary_container_light = 17170531; // 0x1060063
+    field public static final int system_on_secondary_dark = 17170576; // 0x1060090
+    field public static final int system_on_secondary_fixed = 17170618; // 0x10600ba
+    field public static final int system_on_secondary_fixed_variant = 17170619; // 0x10600bb
+    field public static final int system_on_secondary_light = 17170533; // 0x1060065
+    field public static final int system_on_surface_dark = 17170584; // 0x1060098
+    field public static final int system_on_surface_light = 17170541; // 0x106006d
+    field public static final int system_on_surface_variant_dark = 17170593; // 0x10600a1
+    field public static final int system_on_surface_variant_light = 17170550; // 0x1060076
+    field public static final int system_on_tertiary_container_dark = 17170578; // 0x1060092
+    field public static final int system_on_tertiary_container_light = 17170535; // 0x1060067
+    field public static final int system_on_tertiary_dark = 17170580; // 0x1060094
+    field public static final int system_on_tertiary_fixed = 17170622; // 0x10600be
+    field public static final int system_on_tertiary_fixed_variant = 17170623; // 0x10600bf
+    field public static final int system_on_tertiary_light = 17170537; // 0x1060069
+    field public static final int system_outline_dark = 17170594; // 0x10600a2
+    field public static final int system_outline_light = 17170551; // 0x1060077
+    field public static final int system_outline_variant_dark = 17170625; // 0x10600c1
+    field public static final int system_outline_variant_light = 17170624; // 0x10600c0
+    field public static final int system_palette_key_color_neutral_dark = 17170610; // 0x10600b2
+    field public static final int system_palette_key_color_neutral_light = 17170567; // 0x1060087
+    field public static final int system_palette_key_color_neutral_variant_dark = 17170611; // 0x10600b3
+    field public static final int system_palette_key_color_neutral_variant_light = 17170568; // 0x1060088
+    field public static final int system_palette_key_color_primary_dark = 17170607; // 0x10600af
+    field public static final int system_palette_key_color_primary_light = 17170564; // 0x1060084
+    field public static final int system_palette_key_color_secondary_dark = 17170608; // 0x10600b0
+    field public static final int system_palette_key_color_secondary_light = 17170565; // 0x1060085
+    field public static final int system_palette_key_color_tertiary_dark = 17170609; // 0x10600b1
+    field public static final int system_palette_key_color_tertiary_light = 17170566; // 0x1060086
+    field public static final int system_primary_container_dark = 17170569; // 0x1060089
+    field public static final int system_primary_container_light = 17170526; // 0x106005e
+    field public static final int system_primary_dark = 17170571; // 0x106008b
+    field public static final int system_primary_fixed = 17170612; // 0x10600b4
+    field public static final int system_primary_fixed_dim = 17170613; // 0x10600b5
+    field public static final int system_primary_light = 17170528; // 0x1060060
+    field public static final int system_secondary_container_dark = 17170573; // 0x106008d
+    field public static final int system_secondary_container_light = 17170530; // 0x1060062
+    field public static final int system_secondary_dark = 17170575; // 0x106008f
+    field public static final int system_secondary_fixed = 17170616; // 0x10600b8
+    field public static final int system_secondary_fixed_dim = 17170617; // 0x10600b9
+    field public static final int system_secondary_light = 17170532; // 0x1060064
+    field public static final int system_surface_bright_dark = 17170590; // 0x106009e
+    field public static final int system_surface_bright_light = 17170547; // 0x1060073
+    field public static final int system_surface_container_dark = 17170587; // 0x106009b
+    field public static final int system_surface_container_high_dark = 17170588; // 0x106009c
+    field public static final int system_surface_container_high_light = 17170545; // 0x1060071
+    field public static final int system_surface_container_highest_dark = 17170589; // 0x106009d
+    field public static final int system_surface_container_highest_light = 17170546; // 0x1060072
+    field public static final int system_surface_container_light = 17170544; // 0x1060070
+    field public static final int system_surface_container_low_dark = 17170585; // 0x1060099
+    field public static final int system_surface_container_low_light = 17170542; // 0x106006e
+    field public static final int system_surface_container_lowest_dark = 17170586; // 0x106009a
+    field public static final int system_surface_container_lowest_light = 17170543; // 0x106006f
+    field public static final int system_surface_dark = 17170583; // 0x1060097
+    field public static final int system_surface_dim_dark = 17170591; // 0x106009f
+    field public static final int system_surface_dim_light = 17170548; // 0x1060074
+    field public static final int system_surface_light = 17170540; // 0x106006c
+    field public static final int system_surface_variant_dark = 17170592; // 0x10600a0
+    field public static final int system_surface_variant_light = 17170549; // 0x1060075
+    field public static final int system_tertiary_container_dark = 17170577; // 0x1060091
+    field public static final int system_tertiary_container_light = 17170534; // 0x1060066
+    field public static final int system_tertiary_dark = 17170579; // 0x1060093
+    field public static final int system_tertiary_fixed = 17170620; // 0x10600bc
+    field public static final int system_tertiary_fixed_dim = 17170621; // 0x10600bd
+    field public static final int system_tertiary_light = 17170536; // 0x1060068
+    field public static final int system_text_hint_inverse_dark = 17170606; // 0x10600ae
+    field public static final int system_text_hint_inverse_light = 17170563; // 0x1060083
+    field public static final int system_text_primary_inverse_dark = 17170602; // 0x10600aa
+    field public static final int system_text_primary_inverse_disable_only_dark = 17170604; // 0x10600ac
+    field public static final int system_text_primary_inverse_disable_only_light = 17170561; // 0x1060081
+    field public static final int system_text_primary_inverse_light = 17170559; // 0x106007f
+    field public static final int system_text_secondary_and_tertiary_inverse_dark = 17170603; // 0x10600ab
+    field public static final int system_text_secondary_and_tertiary_inverse_disabled_dark = 17170605; // 0x10600ad
+    field public static final int system_text_secondary_and_tertiary_inverse_disabled_light = 17170562; // 0x1060082
+    field public static final int system_text_secondary_and_tertiary_inverse_light = 17170560; // 0x1060080
     field public static final int tab_indicator_text = 17170441; // 0x1060009
     field @Deprecated public static final int tertiary_text_dark = 17170448; // 0x1060010
     field @Deprecated public static final int tertiary_text_light = 17170449; // 0x1060011
@@ -2310,7 +2310,7 @@
     field public static final int accessibilityActionPageUp = 16908358; // 0x1020046
     field public static final int accessibilityActionPressAndHold = 16908362; // 0x102004a
     field public static final int accessibilityActionScrollDown = 16908346; // 0x102003a
-    field public static final int accessibilityActionScrollInDirection;
+    field public static final int accessibilityActionScrollInDirection = 16908382; // 0x102005e
     field public static final int accessibilityActionScrollLeft = 16908345; // 0x1020039
     field public static final int accessibilityActionScrollRight = 16908347; // 0x102003b
     field public static final int accessibilityActionScrollToPosition = 16908343; // 0x1020037
@@ -2331,7 +2331,7 @@
     field public static final int addToDictionary = 16908330; // 0x102002a
     field public static final int autofill = 16908355; // 0x1020043
     field public static final int background = 16908288; // 0x1020000
-    field public static final int bold;
+    field public static final int bold = 16908379; // 0x102005b
     field public static final int button1 = 16908313; // 0x1020019
     field public static final int button2 = 16908314; // 0x102001a
     field public static final int button3 = 16908315; // 0x102001b
@@ -2357,7 +2357,7 @@
     field public static final int inputExtractAccessories = 16908378; // 0x102005a
     field public static final int inputExtractAction = 16908377; // 0x1020059
     field public static final int inputExtractEditText = 16908325; // 0x1020025
-    field public static final int italic;
+    field public static final int italic = 16908380; // 0x102005c
     field @Deprecated public static final int keyboardView = 16908326; // 0x1020026
     field public static final int list = 16908298; // 0x102000a
     field public static final int list_container = 16908351; // 0x102003f
@@ -2389,7 +2389,7 @@
     field public static final int textAssist = 16908353; // 0x1020041
     field public static final int title = 16908310; // 0x1020016
     field public static final int toggle = 16908311; // 0x1020017
-    field public static final int underline;
+    field public static final int underline = 16908381; // 0x102005d
     field public static final int undo = 16908338; // 0x1020032
     field public static final int widget_frame = 16908312; // 0x1020018
   }
@@ -32652,7 +32652,7 @@
     field public static final int S = 31; // 0x1f
     field public static final int S_V2 = 32; // 0x20
     field public static final int TIRAMISU = 33; // 0x21
-    field public static final int UPSIDE_DOWN_CAKE = 10000; // 0x2710
+    field public static final int UPSIDE_DOWN_CAKE = 34; // 0x22
   }
 
   public final class Bundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index fbc69e3..ace7d59 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -410,14 +410,14 @@
     field public static final int sdkVersion = 16844304; // 0x1010610
     field public static final int supportsAmbientMode = 16844173; // 0x101058d
     field public static final int userRestriction = 16844164; // 0x1010584
-    field public static final int visualQueryDetectionService;
+    field public static final int visualQueryDetectionService = 16844410; // 0x101067a
   }
 
   public static final class R.bool {
-    field public static final int config_enableDefaultNotes;
-    field public static final int config_enableDefaultNotesForWorkProfile;
+    field public static final int config_enableDefaultNotes = 17891338; // 0x111000a
+    field public static final int config_enableDefaultNotesForWorkProfile = 17891339; // 0x111000b
     field public static final int config_enableQrCodeScannerOnLockScreen = 17891336; // 0x1110008
-    field public static final int config_safetyProtectionEnabled;
+    field public static final int config_safetyProtectionEnabled = 17891337; // 0x1110009
     field public static final int config_sendPackageName = 17891328; // 0x1110000
     field public static final int config_showDefaultAssistant = 17891329; // 0x1110001
     field public static final int config_showDefaultEmergency = 17891330; // 0x1110002
@@ -430,7 +430,7 @@
 
   public static final class R.dimen {
     field public static final int config_restrictedIconSize = 17104903; // 0x1050007
-    field public static final int config_viewConfigurationHandwritingGestureLineMargin;
+    field public static final int config_viewConfigurationHandwritingGestureLineMargin = 17104906; // 0x105000a
   }
 
   public static final class R.drawable {
@@ -452,7 +452,7 @@
     field public static final int config_defaultCallRedirection = 17039397; // 0x1040025
     field public static final int config_defaultCallScreening = 17039398; // 0x1040026
     field public static final int config_defaultDialer = 17039395; // 0x1040023
-    field public static final int config_defaultNotes;
+    field public static final int config_defaultNotes = 17039429; // 0x1040045
     field public static final int config_defaultSms = 17039396; // 0x1040024
     field public static final int config_devicePolicyManagement = 17039421; // 0x104003d
     field public static final int config_feedbackIntentExtraKey = 17039391; // 0x104001f
@@ -468,10 +468,10 @@
     field public static final int config_systemAutomotiveCalendarSyncManager = 17039423; // 0x104003f
     field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028
     field public static final int config_systemAutomotiveProjection = 17039401; // 0x1040029
-    field public static final int config_systemCallStreaming;
+    field public static final int config_systemCallStreaming = 17039431; // 0x1040047
     field public static final int config_systemCompanionDeviceProvider = 17039417; // 0x1040039
     field public static final int config_systemContacts = 17039403; // 0x104002b
-    field public static final int config_systemFinancedDeviceController;
+    field public static final int config_systemFinancedDeviceController = 17039430; // 0x1040046
     field public static final int config_systemGallery = 17039399; // 0x1040027
     field public static final int config_systemNotificationIntelligence = 17039413; // 0x1040035
     field public static final int config_systemSettingsIntelligence = 17039426; // 0x1040042
@@ -483,7 +483,7 @@
     field public static final int config_systemUi = 17039418; // 0x104003a
     field public static final int config_systemUiIntelligence = 17039410; // 0x1040032
     field public static final int config_systemVisualIntelligence = 17039415; // 0x1040037
-    field public static final int config_systemWearHealthService;
+    field public static final int config_systemWearHealthService = 17039428; // 0x1040044
     field public static final int config_systemWellbeing = 17039408; // 0x1040030
     field public static final int config_systemWifiCoexManager = 17039407; // 0x104002f
     field public static final int safety_protection_display_text = 17039425; // 0x1040041
@@ -10117,7 +10117,7 @@
   public final class NetworkProviderInfo implements android.os.Parcelable {
     method public int describeContents();
     method @IntRange(from=0, to=100) public int getBatteryPercentage();
-    method @IntRange(from=0, to=3) public int getConnectionStrength();
+    method @IntRange(from=0, to=4) public int getConnectionStrength();
     method @NonNull public String getDeviceName();
     method public int getDeviceType();
     method @NonNull public android.os.Bundle getExtras();
@@ -10136,7 +10136,7 @@
     ctor public NetworkProviderInfo.Builder(@NonNull String, @NonNull String);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo build();
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryPercentage(@IntRange(from=0, to=100) int);
-    method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=3) int);
+    method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=4) int);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceName(@NonNull String);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceType(int);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setExtras(@NonNull android.os.Bundle);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 2dfda51..2d9a99c 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -8,8 +8,6 @@
     field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS";
     field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA";
     field public static final String BIND_CELL_BROADCAST_SERVICE = "android.permission.BIND_CELL_BROADCAST_SERVICE";
-    field public static final String BODY_SENSORS_WRIST_TEMPERATURE = "android.permission.BODY_SENSORS_WRIST_TEMPERATURE";
-    field public static final String BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND = "android.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND";
     field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE";
     field public static final String BROADCAST_CLOSE_SYSTEM_DIALOGS = "android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS";
     field public static final String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE";
@@ -779,6 +777,17 @@
 
 }
 
+package android.app.tare {
+
+  public class EconomyManager {
+    method public int getEnabledMode();
+    field public static final int ENABLED_MODE_OFF = 0; // 0x0
+    field public static final int ENABLED_MODE_SHADOW = 2; // 0x2
+    field public static final String KEY_ENABLE_TARE_MODE = "enable_tare_mode";
+  }
+
+}
+
 package android.app.usage {
 
   public class StorageStatsManager {
@@ -1651,6 +1660,11 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.KeyphraseMetadata> CREATOR;
   }
 
+  public class SoundTrigger {
+    field public static final int MODEL_PARAM_INVALID = -1; // 0xffffffff
+    field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0
+  }
+
   public static final class SoundTrigger.KeyphraseRecognitionExtra implements android.os.Parcelable {
     ctor public SoundTrigger.KeyphraseRecognitionExtra(int, int, int);
   }
@@ -1663,6 +1677,19 @@
     ctor public SoundTrigger.ModuleProperties(int, @NonNull String, @NonNull String, @NonNull String, int, @NonNull String, int, int, int, int, boolean, int, boolean, int, boolean, int);
   }
 
+  public static final class SoundTrigger.RecognitionConfig implements android.os.Parcelable {
+    ctor public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[], int);
+    ctor public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[]);
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.RecognitionConfig> CREATOR;
+    field public final boolean allowMultipleTriggers;
+    field public final int audioCapabilities;
+    field public final boolean captureRequested;
+    field @NonNull public final byte[] data;
+    field @NonNull public final android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[] keyphrases;
+  }
+
   public static class SoundTrigger.RecognitionEvent {
     ctor public SoundTrigger.RecognitionEvent(int, int, boolean, int, int, int, boolean, @NonNull android.media.AudioFormat, @Nullable byte[], long);
   }
@@ -2000,6 +2027,57 @@
 
 }
 
+package android.media.soundtrigger {
+
+  public final class SoundTriggerInstrumentation {
+    method public void setResourceContention(boolean);
+    method public void triggerOnResourcesAvailable();
+    method public void triggerRestart();
+  }
+
+  public static interface SoundTriggerInstrumentation.GlobalCallback {
+    method public default void onClientAttached();
+    method public default void onClientDetached();
+    method public default void onFrameworkDetached();
+    method public void onModelLoaded(@NonNull android.media.soundtrigger.SoundTriggerInstrumentation.ModelSession);
+    method public default void onPreempted();
+    method public default void onRestarted();
+  }
+
+  public static interface SoundTriggerInstrumentation.ModelCallback {
+    method public default void onModelUnloaded();
+    method public default void onParamSet(int, int);
+    method public void onRecognitionStarted(@NonNull android.media.soundtrigger.SoundTriggerInstrumentation.RecognitionSession);
+  }
+
+  public class SoundTriggerInstrumentation.ModelSession {
+    method public void clearModelCallback();
+    method @NonNull public java.util.List<android.hardware.soundtrigger.SoundTrigger.Keyphrase> getPhrases();
+    method @NonNull public android.media.soundtrigger.SoundTriggerManager.Model getSoundModel();
+    method public boolean isKeyphrase();
+    method public void setModelCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.soundtrigger.SoundTriggerInstrumentation.ModelCallback);
+    method public void triggerUnloadModel();
+  }
+
+  public static interface SoundTriggerInstrumentation.RecognitionCallback {
+    method public void onRecognitionStopped();
+  }
+
+  public class SoundTriggerInstrumentation.RecognitionSession {
+    method public void clearRecognitionCallback();
+    method public int getAudioSession();
+    method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig getRecognitionConfig();
+    method public void setRecognitionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.soundtrigger.SoundTriggerInstrumentation.RecognitionCallback);
+    method public void triggerAbortRecognition();
+    method public void triggerRecognitionEvent(@NonNull byte[], @Nullable java.util.List<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra>);
+  }
+
+  public final class SoundTriggerManager {
+    method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public static android.media.soundtrigger.SoundTriggerInstrumentation attachInstrumentation(@NonNull java.util.concurrent.Executor, @NonNull android.media.soundtrigger.SoundTriggerInstrumentation.GlobalCallback);
+  }
+
+}
+
 package android.media.tv {
 
   public final class TvInputManager {
@@ -2024,6 +2102,14 @@
 
 }
 
+package android.media.voice {
+
+  public final class KeyphraseModelManager {
+    method @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public void setModelDatabaseForTestEnabled(boolean);
+  }
+
+}
+
 package android.net {
 
   public class NetworkPolicyManager {
@@ -2944,6 +3030,7 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest(@NonNull String, @NonNull java.util.Locale, @NonNull android.hardware.soundtrigger.SoundTrigger.ModuleProperties, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.AlwaysOnHotwordDetector.Callback);
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest(@NonNull String, @NonNull java.util.Locale, @Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull android.hardware.soundtrigger.SoundTrigger.ModuleProperties, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.AlwaysOnHotwordDetector.Callback);
     method @NonNull public final java.util.List<android.hardware.soundtrigger.SoundTrigger.ModuleProperties> listModuleProperties();
+    method public final void setTestModuleForAlwaysOnHotwordDetectorEnabled(boolean);
   }
 
   public static class VoiceInteractionSession.ActivityId {
@@ -3351,8 +3438,14 @@
     method @NonNull public android.hardware.input.InputDeviceIdentifier getIdentifier();
   }
 
+  public abstract class InputEvent implements android.os.Parcelable {
+    method public abstract int getDisplayId();
+    method public abstract void setDisplayId(int);
+  }
+
   public class KeyEvent extends android.view.InputEvent implements android.os.Parcelable {
     method public static String actionToString(int);
+    method public final int getDisplayId();
     method public final void setDisplayId(int);
     field public static final int FLAG_IS_ACCESSIBILITY_EVENT = 2048; // 0x800
     field public static final int LAST_KEYCODE = 316; // 0x13c
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 0293bb5..95e446d 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -343,7 +343,170 @@
      */
     public abstract boolean hasRunningActivity(int uid, @Nullable String packageName);
 
-    public abstract void updateOomAdj();
+    /**
+     * Oom Adj Reason: none - internal use only, do not use it.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_NONE = 0;
+
+    /**
+     * Oom Adj Reason: activity changes.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_ACTIVITY = 1;
+
+    /**
+     * Oom Adj Reason: finishing a broadcast receiver.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_FINISH_RECEIVER = 2;
+
+    /**
+     * Oom Adj Reason: starting a broadcast receiver.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_START_RECEIVER = 3;
+
+    /**
+     * Oom Adj Reason: binding to a service.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_BIND_SERVICE = 4;
+
+    /**
+     * Oom Adj Reason: unbinding from a service.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_UNBIND_SERVICE = 5;
+
+    /**
+     * Oom Adj Reason: starting a service.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_START_SERVICE = 6;
+
+    /**
+     * Oom Adj Reason: connecting to a content provider.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_GET_PROVIDER = 7;
+
+    /**
+     * Oom Adj Reason: disconnecting from a content provider.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_REMOVE_PROVIDER = 8;
+
+    /**
+     * Oom Adj Reason: UI visibility changes.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_UI_VISIBILITY = 9;
+
+    /**
+     * Oom Adj Reason: device power allowlist changes.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_ALLOWLIST = 10;
+
+    /**
+     * Oom Adj Reason: starting a process.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_PROCESS_BEGIN = 11;
+
+    /**
+     * Oom Adj Reason: ending a process.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_PROCESS_END = 12;
+
+    /**
+     * Oom Adj Reason: short FGS timeout.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_SHORT_FGS_TIMEOUT = 13;
+
+    /**
+     * Oom Adj Reason: system initialization.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_SYSTEM_INIT = 14;
+
+    /**
+     * Oom Adj Reason: backup/restore.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_BACKUP = 15;
+
+    /**
+     * Oom Adj Reason: instrumented by the SHELL.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_SHELL = 16;
+
+    /**
+     * Oom Adj Reason: task stack is being removed.
+     */
+    public static final int OOM_ADJ_REASON_REMOVE_TASK = 17;
+
+    /**
+     * Oom Adj Reason: uid idle.
+     */
+    public static final int OOM_ADJ_REASON_UID_IDLE = 18;
+
+    /**
+     * Oom Adj Reason: stop service.
+     */
+    public static final int OOM_ADJ_REASON_STOP_SERVICE = 19;
+
+    /**
+     * Oom Adj Reason: executing service.
+     */
+    public static final int OOM_ADJ_REASON_EXECUTING_SERVICE = 20;
+
+    /**
+     * Oom Adj Reason: background restriction changes.
+     */
+    public static final int OOM_ADJ_REASON_RESTRICTION_CHANGE = 21;
+
+    /**
+     * Oom Adj Reason: A package or its component is disabled.
+     */
+    public static final int OOM_ADJ_REASON_COMPONENT_DISABLED = 22;
+
+    @IntDef(prefix = {"OOM_ADJ_REASON_"}, value = {
+        OOM_ADJ_REASON_NONE,
+        OOM_ADJ_REASON_ACTIVITY,
+        OOM_ADJ_REASON_FINISH_RECEIVER,
+        OOM_ADJ_REASON_START_RECEIVER,
+        OOM_ADJ_REASON_BIND_SERVICE,
+        OOM_ADJ_REASON_UNBIND_SERVICE,
+        OOM_ADJ_REASON_START_SERVICE,
+        OOM_ADJ_REASON_GET_PROVIDER,
+        OOM_ADJ_REASON_REMOVE_PROVIDER,
+        OOM_ADJ_REASON_UI_VISIBILITY,
+        OOM_ADJ_REASON_ALLOWLIST,
+        OOM_ADJ_REASON_PROCESS_BEGIN,
+        OOM_ADJ_REASON_PROCESS_END,
+        OOM_ADJ_REASON_SHORT_FGS_TIMEOUT,
+        OOM_ADJ_REASON_SYSTEM_INIT,
+        OOM_ADJ_REASON_BACKUP,
+        OOM_ADJ_REASON_SHELL,
+        OOM_ADJ_REASON_REMOVE_TASK,
+        OOM_ADJ_REASON_UID_IDLE,
+        OOM_ADJ_REASON_STOP_SERVICE,
+        OOM_ADJ_REASON_EXECUTING_SERVICE,
+        OOM_ADJ_REASON_RESTRICTION_CHANGE,
+        OOM_ADJ_REASON_COMPONENT_DISABLED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface OomAdjReason {}
+
+    /**
+     * Request to update oom adj.
+     */
+    public abstract void updateOomAdj(@OomAdjReason int oomAdjReason);
     public abstract void updateCpuStats();
 
     /**
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index b48a8fb..3312294 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1450,9 +1450,8 @@
     public static final int OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD =
             AppProtoEnums.APP_OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD;
 
-    /** @hide Access to wrist temperature sensors. */
-    public static final int OP_BODY_SENSORS_WRIST_TEMPERATURE =
-            AppProtoEnums.APP_OP_BODY_SENSORS_WRIST_TEMPERATURE;
+    // App op deprecated/removed.
+    private static final int OP_DEPRECATED_2 = AppProtoEnums.APP_OP_BODY_SENSORS_WRIST_TEMPERATURE;
 
     /**
      * Send an intent to launch instead of posting the notification to the status bar.
@@ -1461,9 +1460,25 @@
      */
     public static final int OP_USE_FULL_SCREEN_INTENT = AppProtoEnums.APP_OP_USE_FULL_SCREEN_INTENT;
 
+    /**
+     * Hides camera indicator for sandboxed detection apps that directly access the service.
+     *
+     * @hide
+     */
+    public static final int OP_CAMERA_SANDBOXED =
+            AppProtoEnums.APP_OP_CAMERA_SANDBOXED;
+
+    /**
+     * Hides microphone indicator for sandboxed detection apps that directly access the service.
+     *
+     * @hide
+     */
+    public static final int OP_RECORD_AUDIO_SANDBOXED =
+            AppProtoEnums.APP_OP_RECORD_AUDIO_SANDBOXED;
+
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final int _NUM_OP = 134;
+    public static final int _NUM_OP = 136;
 
     /**
      * All app ops represented as strings.
@@ -1603,8 +1618,9 @@
             OPSTR_SYSTEM_EXEMPT_FROM_HIBERNATION,
             OPSTR_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION,
             OPSTR_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
-            OPSTR_BODY_SENSORS_WRIST_TEMPERATURE,
             OPSTR_USE_FULL_SCREEN_INTENT,
+            OPSTR_CAMERA_SANDBOXED,
+            OPSTR_RECORD_AUDIO_SANDBOXED
     })
     public @interface AppOpString {}
 
@@ -2013,6 +2029,20 @@
     public static final String OPSTR_COARSE_LOCATION_SOURCE = "android:coarse_location_source";
 
     /**
+     * Camera is being recorded in sandboxed detection process.
+     *
+     * @hide
+     */
+    public static final String OPSTR_CAMERA_SANDBOXED = "android:camera_sandboxed";
+
+    /**
+     * Audio is being recorded in sandboxed detection process.
+     *
+     * @hide
+     */
+    public static final String OPSTR_RECORD_AUDIO_SANDBOXED = "android:record_audio_sandboxed";
+
+    /**
      * Allow apps to create the requests to manage the media files without user confirmation.
      *
      * @see android.Manifest.permission#MANAGE_MEDIA
@@ -2189,11 +2219,10 @@
             "android:capture_consentless_bugreport_on_userdebug_build";
 
     /**
-     * Access to wrist temperature body sensors.
+     * App op deprecated/removed.
      * @hide
      */
-    public static final String OPSTR_BODY_SENSORS_WRIST_TEMPERATURE =
-            "android:body_sensors_wrist_temperature";
+    public static final String OPSTR_DEPRECATED_2 = "android:deprecated_2";
 
     /**
      * Send an intent to launch instead of posting the notification to the status bar.
@@ -2311,7 +2340,6 @@
             OP_READ_MEDIA_VISUAL_USER_SELECTED,
             OP_FOREGROUND_SERVICE_SPECIAL_USE,
             OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
-            OP_BODY_SENSORS_WRIST_TEMPERATURE,
             OP_USE_FULL_SCREEN_INTENT
     };
 
@@ -2731,14 +2759,15 @@
                 "CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD")
                 .setPermission(Manifest.permission.CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD)
                 .build(),
-        new AppOpInfo.Builder(OP_BODY_SENSORS_WRIST_TEMPERATURE,
-                OPSTR_BODY_SENSORS_WRIST_TEMPERATURE,
-                "BODY_SENSORS_WRIST_TEMPERATURE")
-                .setPermission(Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE)
-                .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_DEPRECATED_2, OPSTR_DEPRECATED_2, "DEPRECATED_2")
+                .setDefaultMode(AppOpsManager.MODE_IGNORED).build(),
         new AppOpInfo.Builder(OP_USE_FULL_SCREEN_INTENT, OPSTR_USE_FULL_SCREEN_INTENT,
                 "USE_FULL_SCREEN_INTENT").setPermission(Manifest.permission.USE_FULL_SCREEN_INTENT)
-                .build()
+                .build(),
+        new AppOpInfo.Builder(OP_CAMERA_SANDBOXED, OPSTR_CAMERA_SANDBOXED,
+            "CAMERA_SANDBOXED").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_RECORD_AUDIO_SANDBOXED, OPSTR_RECORD_AUDIO_SANDBOXED,
+                "RECORD_AUDIO_SANDBOXED").setDefaultMode(AppOpsManager.MODE_ALLOWED).build()
     };
 
     // The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java
index be012cf..c0c59a2 100644
--- a/core/java/android/app/ForegroundServiceTypePolicy.java
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -473,7 +473,6 @@
             new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
                 new RegularPermission(Manifest.permission.ACTIVITY_RECOGNITION),
                 new RegularPermission(Manifest.permission.BODY_SENSORS),
-                new RegularPermission(Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE),
                 new RegularPermission(Manifest.permission.HIGH_SAMPLING_RATE_SENSORS),
             }, false),
             FGS_TYPE_PERM_ENFORCEMENT_FLAG_HEALTH /* permissionEnforcementFlag */,
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index ee24263..2b15589 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -220,6 +220,20 @@
     void notifyGoingToSleep(int x, int y, in Bundle extras);
 
     /**
+     * Called when the screen has been fully turned on and is visible.
+     *
+     * @hide
+     */
+    void notifyScreenTurnedOn(int displayId);
+
+    /**
+     * Called when the screen starts turning on.
+     *
+     * @hide
+     */
+    void notifyScreenTurningOn(int displayId);
+
+    /**
      * Sets the wallpaper dim amount between [0f, 1f] which would be blended with the system default
      * dimming. 0f doesn't add any additional dimming and 1f makes the wallpaper fully black.
      *
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index c131ce5..e31486f 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -2354,8 +2354,7 @@
                 return mUiAutomation;
             }
             if (mustCreateNewAutomation) {
-                mUiAutomation = new UiAutomation(getTargetContext().getMainLooper(),
-                        mUiAutomationConnection);
+                mUiAutomation = new UiAutomation(getTargetContext(), mUiAutomationConnection);
             } else {
                 mUiAutomation.disconnect();
             }
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index 1df8602..bc5f7f4 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -110,6 +110,9 @@
             "options": [
                 {
                     "exclude-annotation": "androidx.test.filters.FlakyTest"
+                },
+                {
+                    "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceStressTest"
                 }
             ],
             "file_patterns": ["(/|^)VoiceInteract[^/]*"]
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 658e084..247d5bc 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -16,6 +16,8 @@
 
 package android.app;
 
+import static android.view.Display.DEFAULT_DISPLAY;
+
 import android.accessibilityservice.AccessibilityGestureEvent;
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityService.Callbacks;
@@ -30,6 +32,7 @@
 import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -45,6 +48,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.ArraySet;
 import android.util.DebugUtils;
 import android.util.Log;
@@ -69,8 +73,10 @@
 import android.view.inputmethod.EditorInfo;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback;
 import com.android.internal.inputmethod.RemoteAccessibilityInputConnection;
+import com.android.internal.util.Preconditions;
 import com.android.internal.util.function.pooled.PooledLambda;
 
 import libcore.io.IoUtils;
@@ -202,6 +208,8 @@
 
     private final IUiAutomationConnection mUiAutomationConnection;
 
+    private final int mDisplayId;
+
     private HandlerThread mRemoteCallbackThread;
 
     private IAccessibilityServiceClient mClient;
@@ -261,24 +269,49 @@
 
     /**
      * Creates a new instance that will handle callbacks from the accessibility
+     * layer on the thread of the provided context main looper and perform requests for privileged
+     * operations on the provided connection, and filtering display-related features to the display
+     * associated with the context (or the user running the test, on devices that
+     * {@link UserManager#isVisibleBackgroundUsersSupported() support visible background users}).
+     *
+     * @param context the context associated with the automation
+     * @param connection The connection for performing privileged operations.
+     *
+     * @hide
+     */
+    public UiAutomation(Context context, IUiAutomationConnection connection) {
+        this(getDisplayId(context), context.getMainLooper(), connection);
+    }
+
+    /**
+     * Creates a new instance that will handle callbacks from the accessibility
      * layer on the thread of the provided looper and perform requests for privileged
      * operations on the provided connection.
      *
      * @param looper The looper on which to execute accessibility callbacks.
      * @param connection The connection for performing privileged operations.
      *
+     * @deprecated use {@link #UiAutomation(Context, IUiAutomationConnection)} instead
+     *
      * @hide
      */
+    @Deprecated
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public UiAutomation(Looper looper, IUiAutomationConnection connection) {
-        if (looper == null) {
-            throw new IllegalArgumentException("Looper cannot be null!");
-        }
-        if (connection == null) {
-            throw new IllegalArgumentException("Connection cannot be null!");
-        }
+        this(DEFAULT_DISPLAY, looper, connection);
+        Log.w(LOG_TAG, "Created with deprecatead constructor, assumes DEFAULT_DISPLAY");
+    }
+
+    private UiAutomation(int displayId, Looper looper, IUiAutomationConnection connection) {
+        Preconditions.checkArgument(looper != null, "Looper cannot be null!");
+        Preconditions.checkArgument(connection != null, "Connection cannot be null!");
+
         mLocalCallbackHandler = new Handler(looper);
         mUiAutomationConnection = connection;
+        mDisplayId = displayId;
+
+        Log.i(LOG_TAG, "Initialized for user " + Process.myUserHandle().getIdentifier()
+                + " on display " + mDisplayId);
     }
 
     /**
@@ -719,8 +752,14 @@
     }
 
     /**
-     * Gets the windows on the screen of the default display. This method returns only the windows
-     * that a sighted user can interact with, as opposed to all windows.
+     * Gets the windows on the screen associated with the {@link UiAutomation} context (usually the
+     * {@link android.view.Display#DEFAULT_DISPLAY default display).
+     *
+     * <p>
+     * This method returns only the windows that a sighted user can interact with, as opposed to
+     * all windows.
+
+     * <p>
      * For example, if there is a modal dialog shown and the user cannot touch
      * anything behind it, then only the modal window will be reported
      * (assuming it is the top one). For convenience the returned windows
@@ -730,21 +769,23 @@
      * <strong>Note:</strong> In order to access the windows you have to opt-in
      * to retrieve the interactive windows by setting the
      * {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} flag.
-     * </p>
      *
      * @return The windows if there are windows such, otherwise an empty list.
      * @throws IllegalStateException If the connection to the accessibility subsystem is not
      *            established.
      */
     public List<AccessibilityWindowInfo> getWindows() {
+        if (DEBUG) {
+            Log.d(LOG_TAG, "getWindows(): returning windows for display " + mDisplayId);
+        }
         final int connectionId;
         synchronized (mLock) {
             throwIfNotConnectedLocked();
             connectionId = mConnectionId;
         }
         // Calling out without a lock held.
-        return AccessibilityInteractionClient.getInstance()
-                .getWindows(connectionId);
+        return AccessibilityInteractionClient.getInstance().getWindowsOnDisplay(connectionId,
+                mDisplayId);
     }
 
     /**
@@ -1112,8 +1153,10 @@
      * @return The screenshot bitmap on success, null otherwise.
      */
     public Bitmap takeScreenshot() {
-        Display display = DisplayManagerGlobal.getInstance()
-                .getRealDisplay(Display.DEFAULT_DISPLAY);
+        if (DEBUG) {
+            Log.d(LOG_TAG, "Taking screenshot of display " + mDisplayId);
+        }
+        Display display = DisplayManagerGlobal.getInstance().getRealDisplay(mDisplayId);
         Point displaySize = new Point();
         display.getRealSize(displaySize);
 
@@ -1126,10 +1169,12 @@
             screenShot = mUiAutomationConnection.takeScreenshot(
                     new Rect(0, 0, displaySize.x, displaySize.y));
             if (screenShot == null) {
+                Log.e(LOG_TAG, "mUiAutomationConnection.takeScreenshot() returned null for display "
+                        + mDisplayId);
                 return null;
             }
         } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error while taking screenshot!", re);
+            Log.e(LOG_TAG, "Error while taking screenshot of display " + mDisplayId, re);
             return null;
         }
 
@@ -1509,6 +1554,14 @@
         return executeShellCommandInternal(command, true /* includeStderr */);
     }
 
+    /**
+     * @hide
+     */
+    @VisibleForTesting
+    public int getDisplayId() {
+        return mDisplayId;
+    }
+
     private ParcelFileDescriptor[] executeShellCommandInternal(
             String command, boolean includeStderr) {
         warnIfBetterCommand(command);
@@ -1564,6 +1617,7 @@
         final StringBuilder stringBuilder = new StringBuilder();
         stringBuilder.append("UiAutomation@").append(Integer.toHexString(hashCode()));
         stringBuilder.append("[id=").append(mConnectionId);
+        stringBuilder.append(", displayId=").append(mDisplayId);
         stringBuilder.append(", flags=").append(mFlags);
         stringBuilder.append("]");
         return stringBuilder.toString();
@@ -1601,6 +1655,55 @@
         return (mFlags & UiAutomation.FLAG_DONT_USE_ACCESSIBILITY) == 0;
     }
 
+    /**
+     * Gets the display id associated with the UiAutomation context.
+     *
+     * <p><b>NOTE: </b> must be a static method because it's called from a constructor to call
+     * another one.
+     */
+    private static int getDisplayId(Context context) {
+        Preconditions.checkArgument(context != null, "Context cannot be null!");
+
+        UserManager userManager = context.getSystemService(UserManager.class);
+        // TODO(b/255426725): given that this is a temporary solution until a11y supports multiple
+        // users, the display is only set on devices that support that
+        if (!userManager.isVisibleBackgroundUsersSupported()) {
+            return DEFAULT_DISPLAY;
+        }
+
+        int displayId = context.getDisplayId();
+        if (displayId == Display.INVALID_DISPLAY) {
+            // Shouldn't happen, but we better handle it
+            Log.e(LOG_TAG, "UiAutomation created UI context with invalid display id, assuming it's"
+                    + " running in the display assigned to the user");
+            return getMainDisplayIdAssignedToUser(context, userManager);
+        }
+
+        if (displayId != DEFAULT_DISPLAY) {
+            if (DEBUG) {
+                Log.d(LOG_TAG, "getDisplayId(): returning context's display (" + displayId + ")");
+            }
+            // Context is explicitly setting the display, so we respect that...
+            return displayId;
+        }
+        // ...otherwise, we need to get the display the test's user is running on
+        int userDisplayId = getMainDisplayIdAssignedToUser(context, userManager);
+        if (DEBUG) {
+            Log.d(LOG_TAG, "getDisplayId(): returning user's display (" + userDisplayId + ")");
+        }
+        return userDisplayId;
+    }
+
+    private static int getMainDisplayIdAssignedToUser(Context context, UserManager userManager) {
+        if (!userManager.isUserVisible()) {
+            // Should also not happen, but ...
+            Log.e(LOG_TAG, "User (" + context.getUserId() + ") is not visible, using "
+                    + "DEFAULT_DISPLAY");
+            return DEFAULT_DISPLAY;
+        }
+        return userManager.getMainDisplayIdAssignedToUser();
+    }
+
     private class IAccessibilityServiceClientImpl extends IAccessibilityServiceClientWrapper {
 
         public IAccessibilityServiceClientImpl(Looper looper, int generationId) {
@@ -1621,6 +1724,7 @@
                     if (DEBUG) {
                         Log.d(LOG_TAG, "init(): connectionId=" + connectionId + ", windowToken="
                                 + windowToken + ", user=" + Process.myUserHandle()
+                                + ", UiAutomation.mDisplay=" + UiAutomation.this.mDisplayId
                                 + ", mGenerationId=" + mGenerationId
                                 + ", UiAutomation.mGenerationId="
                                 + UiAutomation.this.mGenerationId);
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 13e800e..d96a9d1 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -22,6 +22,7 @@
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.graphics.Bitmap;
@@ -117,7 +118,8 @@
                 throw new IllegalStateException("Already connected.");
             }
             mOwningUid = Binder.getCallingUid();
-            registerUiTestAutomationServiceLocked(client, flags);
+            registerUiTestAutomationServiceLocked(client,
+                    Binder.getCallingUserHandle().getIdentifier(), flags);
             storeRotationStateLocked();
         }
     }
@@ -553,7 +555,7 @@
     }
 
     private void registerUiTestAutomationServiceLocked(IAccessibilityServiceClient client,
-            int flags) {
+            @UserIdInt int userId, int flags) {
         IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
                 ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
         final AccessibilityServiceInfo info = new AccessibilityServiceInfo();
@@ -571,10 +573,11 @@
         try {
             // Calling out with a lock held is fine since if the system
             // process is gone the client calling in will be killed.
-            manager.registerUiTestAutomationService(mToken, client, info, flags);
+            manager.registerUiTestAutomationService(mToken, client, info, userId, flags);
             mClient = client;
         } catch (RemoteException re) {
-            throw new IllegalStateException("Error while registering UiTestAutomationService.", re);
+            throw new IllegalStateException("Error while registering UiTestAutomationService for "
+                    + "user " + userId + ".", re);
         }
     }
 
diff --git a/core/java/android/credentials/ui/CreateCredentialProviderData.java b/core/java/android/credentials/ui/CreateCredentialProviderData.java
index 852934a..629d578 100644
--- a/core/java/android/credentials/ui/CreateCredentialProviderData.java
+++ b/core/java/android/credentials/ui/CreateCredentialProviderData.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.TestApi;
+import android.content.pm.ParceledListSlice;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -35,7 +36,7 @@
 @TestApi
 public final class CreateCredentialProviderData extends ProviderData implements Parcelable {
     @NonNull
-    private final List<Entry> mSaveEntries;
+    private final ParceledListSlice<Entry> mSaveEntries;
     @Nullable
     private final Entry mRemoteEntry;
 
@@ -43,13 +44,13 @@
             @NonNull String providerFlattenedComponentName, @NonNull List<Entry> saveEntries,
             @Nullable Entry remoteEntry) {
         super(providerFlattenedComponentName);
-        mSaveEntries = saveEntries;
+        mSaveEntries = new ParceledListSlice<>(saveEntries);
         mRemoteEntry = remoteEntry;
     }
 
     @NonNull
     public List<Entry> getSaveEntries() {
-        return mSaveEntries;
+        return mSaveEntries.getList();
     }
 
     @Nullable
@@ -60,9 +61,7 @@
     private CreateCredentialProviderData(@NonNull Parcel in) {
         super(in);
 
-        List<Entry> credentialEntries = new ArrayList<>();
-        in.readTypedList(credentialEntries, Entry.CREATOR);
-        mSaveEntries = credentialEntries;
+        mSaveEntries = in.readParcelable(null, android.content.pm.ParceledListSlice.class);
         AnnotationValidations.validate(NonNull.class, null, mSaveEntries);
 
         Entry remoteEntry = in.readTypedObject(Entry.CREATOR);
@@ -72,7 +71,7 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         super.writeToParcel(dest, flags);
-        dest.writeTypedList(mSaveEntries);
+        dest.writeParcelable(mSaveEntries, flags);
         dest.writeTypedObject(mRemoteEntry, flags);
     }
 
diff --git a/core/java/android/credentials/ui/GetCredentialProviderData.java b/core/java/android/credentials/ui/GetCredentialProviderData.java
index e4688a84..773dee9 100644
--- a/core/java/android/credentials/ui/GetCredentialProviderData.java
+++ b/core/java/android/credentials/ui/GetCredentialProviderData.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.TestApi;
+import android.content.pm.ParceledListSlice;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -35,11 +36,11 @@
 @TestApi
 public final class GetCredentialProviderData extends ProviderData implements Parcelable {
     @NonNull
-    private final List<Entry> mCredentialEntries;
+    private final ParceledListSlice<Entry> mCredentialEntries;
     @NonNull
-    private final List<Entry> mActionChips;
+    private final ParceledListSlice<Entry> mActionChips;
     @NonNull
-    private final List<AuthenticationEntry> mAuthenticationEntries;
+    private final ParceledListSlice<AuthenticationEntry> mAuthenticationEntries;
     @Nullable
     private final Entry mRemoteEntry;
 
@@ -49,25 +50,25 @@
             @NonNull List<AuthenticationEntry> authenticationEntries,
             @Nullable Entry remoteEntry) {
         super(providerFlattenedComponentName);
-        mCredentialEntries = credentialEntries;
-        mActionChips = actionChips;
-        mAuthenticationEntries = authenticationEntries;
+        mCredentialEntries = new ParceledListSlice<>(credentialEntries);
+        mActionChips = new ParceledListSlice<>(actionChips);
+        mAuthenticationEntries = new ParceledListSlice<>(authenticationEntries);
         mRemoteEntry = remoteEntry;
     }
 
     @NonNull
     public List<Entry> getCredentialEntries() {
-        return mCredentialEntries;
+        return mCredentialEntries.getList();
     }
 
     @NonNull
     public List<Entry> getActionChips() {
-        return mActionChips;
+        return mActionChips.getList();
     }
 
     @NonNull
     public List<AuthenticationEntry> getAuthenticationEntries() {
-        return mAuthenticationEntries;
+        return mAuthenticationEntries.getList();
     }
 
     @Nullable
@@ -77,20 +78,16 @@
 
     private GetCredentialProviderData(@NonNull Parcel in) {
         super(in);
-
-        List<Entry> credentialEntries = new ArrayList<>();
-        in.readTypedList(credentialEntries, Entry.CREATOR);
-        mCredentialEntries = credentialEntries;
+        mCredentialEntries = in.readParcelable(null,
+                android.content.pm.ParceledListSlice.class);
         AnnotationValidations.validate(NonNull.class, null, mCredentialEntries);
 
-        List<Entry> actionChips  = new ArrayList<>();
-        in.readTypedList(actionChips, Entry.CREATOR);
-        mActionChips = actionChips;
+        mActionChips = in.readParcelable(null,
+                android.content.pm.ParceledListSlice.class);
         AnnotationValidations.validate(NonNull.class, null, mActionChips);
 
-        List<AuthenticationEntry> authenticationEntries  = new ArrayList<>();
-        in.readTypedList(authenticationEntries, AuthenticationEntry.CREATOR);
-        mAuthenticationEntries = authenticationEntries;
+        mAuthenticationEntries = in.readParcelable(null,
+                android.content.pm.ParceledListSlice.class);
         AnnotationValidations.validate(NonNull.class, null, mAuthenticationEntries);
 
         Entry remoteEntry = in.readTypedObject(Entry.CREATOR);
@@ -100,9 +97,9 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         super.writeToParcel(dest, flags);
-        dest.writeTypedList(mCredentialEntries);
-        dest.writeTypedList(mActionChips);
-        dest.writeTypedList(mAuthenticationEntries);
+        dest.writeParcelable(mCredentialEntries, flags);
+        dest.writeParcelable(mActionChips, flags);
+        dest.writeParcelable(mAuthenticationEntries, flags);
         dest.writeTypedObject(mRemoteEntry, flags);
     }
 
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index fa678fc..2e40f60 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -142,6 +142,7 @@
         private PromptInfo mPromptInfo;
         private ButtonInfo mNegativeButtonInfo;
         private Context mContext;
+        private IAuthService mService;
 
         /**
          * Creates a builder for a {@link BiometricPrompt} dialog.
@@ -212,6 +213,18 @@
         }
 
         /**
+         * @param service
+         * @return This builder.
+         * @hide
+         */
+        @RequiresPermission(TEST_BIOMETRIC)
+        @NonNull
+        public Builder setService(@NonNull IAuthService service) {
+            mService = service;
+            return this;
+        }
+
+        /**
          * Sets an optional title, subtitle, and/or description that will override other text when
          * the user is authenticating with PIN/pattern/password. Currently for internal use only.
          * @return This builder.
@@ -472,7 +485,9 @@
                 throw new IllegalArgumentException("Can't have both negative button behavior"
                         + " and device credential enabled");
             }
-            return new BiometricPrompt(mContext, mPromptInfo, mNegativeButtonInfo);
+            mService = (mService == null) ? IAuthService.Stub.asInterface(
+                    ServiceManager.getService(Context.AUTH_SERVICE)) : mService;
+            return new BiometricPrompt(mContext, mPromptInfo, mNegativeButtonInfo, mService);
         }
     }
 
@@ -521,7 +536,6 @@
         public void onAuthenticationFailed() {
             mExecutor.execute(() -> {
                 mAuthenticationCallback.onAuthenticationFailed();
-                mIsPromptShowing = false;
             });
         }
 
@@ -604,12 +618,12 @@
 
     private boolean mIsPromptShowing;
 
-    private BiometricPrompt(Context context, PromptInfo promptInfo, ButtonInfo negativeButtonInfo) {
+    private BiometricPrompt(Context context, PromptInfo promptInfo, ButtonInfo negativeButtonInfo,
+            IAuthService service) {
         mContext = context;
         mPromptInfo = promptInfo;
         mNegativeButtonInfo = negativeButtonInfo;
-        mService = IAuthService.Stub.asInterface(
-                ServiceManager.getService(Context.AUTH_SERVICE));
+        mService = service;
         mIsPromptShowing = false;
     }
 
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 5feda78..ad68866 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -1310,6 +1310,10 @@
      * {@link Surface}, submitting a reprocess {@link CaptureRequest} with multiple
      * output targets will result in a {@link CaptureFailure}.
      *
+     * From Android 14 onward, {@link CaptureRequest#CONTROL_CAPTURE_INTENT} will be set to
+     * {@link CameraMetadata#CONTROL_CAPTURE_INTENT_STILL_CAPTURE} by default. Prior to Android 14,
+     * apps will need to explicitly set this key themselves.
+     *
      * @param inputResult The capture result of the output image or one of the output images used
      *                       to generate the reprocess input image for this capture request.
      *
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index cb1efe8..f2d8caa 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -26,6 +26,7 @@
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CameraExtensionCharacteristics;
+import android.hardware.camera2.CameraMetadata;
 import android.hardware.camera2.CameraOfflineSession;
 import android.hardware.camera2.CaptureFailure;
 import android.hardware.camera2.CaptureRequest;
@@ -861,8 +862,13 @@
             CameraMetadataNative resultMetadata = new
                     CameraMetadataNative(inputResult.getNativeCopy());
 
-            return new CaptureRequest.Builder(resultMetadata, /*reprocess*/true,
-                    inputResult.getSessionId(), getId(), /*physicalCameraIdSet*/ null);
+            CaptureRequest.Builder builder = new CaptureRequest.Builder(resultMetadata,
+                    /*reprocess*/true, inputResult.getSessionId(), getId(),
+                    /*physicalCameraIdSet*/ null);
+            builder.set(CaptureRequest.CONTROL_CAPTURE_INTENT,
+                    CameraMetadata.CONTROL_CAPTURE_INTENT_STILL_CAPTURE);
+
+            return builder;
         }
     }
 
diff --git a/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl b/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl
index dcc3369..4663730 100644
--- a/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl
+++ b/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl
@@ -42,12 +42,6 @@
     void onGenericSoundTriggerDetected(in SoundTrigger.GenericRecognitionEvent recognitionEvent);
 
     /**
-     * Called when the detection fails due to an error.
-     *
-     * @param status The error code that was seen.
-     */
-    void onError(int status);
-    /**
      * Called when the recognition is paused temporarily for some reason.
      */
     void onRecognitionPaused();
@@ -55,4 +49,28 @@
      * Called when the recognition is resumed after it was temporarily paused.
      */
     void onRecognitionResumed();
+
+    // Error callbacks to follow
+    /**
+     * Called when this recognition has been preempted by another.
+     */
+    void onPreempted();
+
+    /**
+     * Called when the underlying ST module service has died.
+     */
+    void onModuleDied();
+
+    /**
+     * Called when the service failed to gracefully resume recognition following a pause.
+     * @param status - The received error code.
+     */
+    void onResumeFailed(int status);
+
+    /**
+     * Called when the service failed to pause recognition when required.
+     * TODO(b/276507281) Remove. This should never happen, so we should abort instead.
+     * @param status - The received error code.
+     */
+    void onPauseFailed(int status);
 }
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index fa16e16..6d43ddf 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -1051,6 +1051,29 @@
             return "ModelParamRange [start=" + mStart + ", end=" + mEnd + "]";
         }
     }
+    /**
+     * SoundTrigger model parameter types.
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "MODEL_PARAM" }, value = {
+            MODEL_PARAM_INVALID,
+            MODEL_PARAM_THRESHOLD_FACTOR
+    })
+    public @interface ModelParamTypes {}
+
+    /**
+     * See {@link ModelParams.INVALID}
+     * @hide
+     */
+    @TestApi
+    public static final int MODEL_PARAM_INVALID = ModelParams.INVALID;
+    /**
+     * See {@link ModelParams.THRESHOLD_FACTOR}
+     * @hide
+     */
+    @TestApi
+    public static final int MODEL_PARAM_THRESHOLD_FACTOR = ModelParams.THRESHOLD_FACTOR;
 
     /**
      * Modes for key phrase recognition
@@ -1450,7 +1473,8 @@
      *
      *  @hide
      */
-    public static class RecognitionConfig implements Parcelable {
+    @TestApi
+    public static final class RecognitionConfig implements Parcelable {
         /** True if the DSP should capture the trigger sound and make it available for further
          * capture. */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -1464,6 +1488,7 @@
          * options for each keyphrase. */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         @NonNull
+        @SuppressLint("ArrayReturn")
         public final KeyphraseRecognitionExtra keyphrases[];
         /** Opaque data for use by system applications who know about voice engine internals,
          * typically during enrollment. */
@@ -1479,8 +1504,8 @@
         public final int audioCapabilities;
 
         public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
-                @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data,
-                int audioCapabilities) {
+                @SuppressLint("ArrayReturn") @Nullable KeyphraseRecognitionExtra[] keyphrases,
+                @Nullable byte[] data, int audioCapabilities) {
             this.captureRequested = captureRequested;
             this.allowMultipleTriggers = allowMultipleTriggers;
             this.keyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0];
@@ -1490,7 +1515,8 @@
 
         @UnsupportedAppUsage
         public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
-                @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) {
+                @SuppressLint("ArrayReturn") @Nullable KeyphraseRecognitionExtra[] keyphrases,
+                @Nullable byte[] data) {
             this(captureRequested, allowMultipleTriggers, keyphrases, data, 0);
         }
 
@@ -1517,7 +1543,7 @@
         }
 
         @Override
-        public void writeToParcel(Parcel dest, int flags) {
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
             dest.writeByte((byte) (captureRequested ? 1 : 0));
             dest.writeByte((byte) (allowMultipleTriggers ? 1 : 0));
             dest.writeTypedArray(keyphrases, flags);
diff --git a/core/java/android/inputmethodservice/InkWindow.java b/core/java/android/inputmethodservice/InkWindow.java
index 15ed450..24d1c95 100644
--- a/core/java/android/inputmethodservice/InkWindow.java
+++ b/core/java/android/inputmethodservice/InkWindow.java
@@ -195,6 +195,7 @@
         Objects.requireNonNull(decor);
         final ViewRootImpl viewRoot = decor.getViewRootImpl();
         Objects.requireNonNull(viewRoot);
-        viewRoot.enqueueInputEvent(event);
+        // The view root will own the event that we enqueue, so provide a copy of the event.
+        viewRoot.enqueueInputEvent(MotionEvent.obtain(event));
     }
 }
diff --git a/core/java/android/nfc/tech/NdefFormatable.java b/core/java/android/nfc/tech/NdefFormatable.java
index f19d302..2240fe7 100644
--- a/core/java/android/nfc/tech/NdefFormatable.java
+++ b/core/java/android/nfc/tech/NdefFormatable.java
@@ -124,6 +124,9 @@
         try {
             int serviceHandle = mTag.getServiceHandle();
             INfcTag tagService = mTag.getTagService();
+            if (tagService == null) {
+                throw new IOException();
+            }
             int errorCode = tagService.formatNdef(serviceHandle, MifareClassic.KEY_DEFAULT);
             switch (errorCode) {
                 case ErrorCodes.SUCCESS:
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 244632a..7383e63 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -493,7 +493,7 @@
          * @hide
          */
         @TestApi
-        public static final int RESOURCES_SDK_INT = SDK_INT + ACTIVE_CODENAMES.length;
+        public static final int RESOURCES_SDK_INT = SDK_INT;
 
         /**
          * The current lowest supported value of app target SDK. Applications targeting
@@ -1222,7 +1222,7 @@
         /**
          * Upside Down Cake.
          */
-        public static final int UPSIDE_DOWN_CAKE = CUR_DEVELOPMENT;
+        public static final int UPSIDE_DOWN_CAKE = 34;
     }
 
     /** The type of build, like "user" or "eng". */
diff --git a/core/java/android/os/image/DynamicSystemClient.java b/core/java/android/os/image/DynamicSystemClient.java
index 63259ed..218ecc8 100644
--- a/core/java/android/os/image/DynamicSystemClient.java
+++ b/core/java/android/os/image/DynamicSystemClient.java
@@ -202,6 +202,13 @@
     public static final String ACTION_NOTIFY_IF_IN_USE =
             "android.os.image.action.NOTIFY_IF_IN_USE";
 
+    /**
+     * Intent action: hide notifications about the status of {@code DynamicSystem}.
+     * @hide
+     */
+    public static final String ACTION_HIDE_NOTIFICATION =
+            "android.os.image.action.HIDE_NOTIFICATION";
+
     /*
      * Intent Keys
      */
@@ -217,6 +224,28 @@
      */
     public static final String KEY_USERDATA_SIZE = "KEY_USERDATA_SIZE";
 
+    /**
+     * Intent key: Whether to enable DynamicSystem immediately after installation is done.
+     *             Note this will reboot the device automatically.
+     * @hide
+     */
+    public static final String KEY_ENABLE_WHEN_COMPLETED = "KEY_ENABLE_WHEN_COMPLETED";
+
+    /**
+     * Intent key: Whether to leave DynamicSystem on device reboot.
+     *             False indicates a sticky mode where device stays in DynamicSystem across reboots.
+     * @hide
+     */
+    public static final String KEY_ONE_SHOT = "KEY_ONE_SHOT";
+
+    /**
+     * Intent key: Whether to use default strings when showing the dialog that prompts
+     *             user for device credentials.
+     *             False indicates using the custom strings provided by {@code DynamicSystem}.
+     * @hide
+     */
+    public static final String KEY_KEYGUARD_USE_DEFAULT_STRINGS =
+            "KEY_KEYGUARD_USE_DEFAULT_STRINGS";
 
     private static class IncomingHandler extends Handler {
         private final WeakReference<DynamicSystemClient> mWeakClient;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7cb959d..aa5a0d0 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2585,7 +2585,7 @@
      * <p>
      * To start an activity with this intent, apps should set the wellbeing package explicitly in
      * the intent together with this action. The wellbeing package is defined in
-     * {@code com.android.internal.R.string.config_defaultWellbeingPackage}.
+     * {@code com.android.internal.R.string.config_systemWellbeing}.
      * <p>
      * Output: Nothing
      *
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index d2f9ff0..59b945c 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -2051,6 +2051,14 @@
          * <P>Type: TEXT</P>
          */
         public static final String ADDRESS = "address";
+
+        /**
+         * The subscription to which the message belongs to. Its value will be less than 0
+         * if the sub id cannot be determined.
+         * <p>Type: INTEGER (long) </p>
+         * @hide
+         */
+        public static final String SUBSCRIPTION_ID = "sub_id";
     }
 
     /**
@@ -2119,6 +2127,14 @@
          * <P>Type: INTEGER (boolean)</P>
          */
         public static final String ARCHIVED = "archived";
+
+        /**
+         * The subscription to which the message belongs to. Its value will be less than 0
+         * if the sub id cannot be determined.
+         * <p>Type: INTEGER (long) </p>
+         * @hide
+         */
+        public static final String SUBSCRIPTION_ID = "sub_id";
     }
 
     /**
@@ -2477,6 +2493,14 @@
             public static final String CHARSET = "charset";
 
             /**
+             * The subscription to which the message belongs to. Its value will be less than 0
+             * if the sub id cannot be determined.
+             * <p>Type: INTEGER (long) </p>
+             * @hide
+             */
+            public static final String SUBSCRIPTION_ID = "sub_id";
+
+            /**
              * Generates a Addr {@link Uri} for message, used to perform Addr table operation
              * for mms.
              *
@@ -2597,6 +2621,14 @@
             public static final String TEXT = "text";
 
             /**
+             * The subscription to which the message belongs to. Its value will be less than 0
+             * if the sub id cannot be determined.
+             * <p>Type: INTEGER (long) </p>
+             * @hide
+             */
+            public static final String SUBSCRIPTION_ID = "sub_id";
+
+            /**
              * Generates a Part {@link Uri} for message, used to perform Part table operation
              * for mms.
              *
@@ -2635,6 +2667,14 @@
              * <P>Type: INTEGER (long)</P>
              */
             public static final String SENT_TIME = "sent_time";
+
+            /**
+             * The subscription to which the message belongs to. Its value will be less than 0
+             * if the sub id cannot be determined.
+             * <p>Type: INTEGER (long) </p>
+             * @hide
+             */
+            public static final String SUBSCRIPTION_ID = "sub_id";
         }
 
         /**
@@ -2868,6 +2908,14 @@
              * <P>Type: TEXT</P>
              */
             public static final String INDEXED_TEXT = "index_text";
+
+            /**
+             * The subscription to which the message belongs to. Its value will be less than 0
+             * if the sub id cannot be determined.
+             * <p>Type: INTEGER (long) </p>
+             * @hide
+             */
+            public static final String SUBSCRIPTION_ID = "sub_id";
         }
     }
 
diff --git a/core/java/android/service/credentials/BeginCreateCredentialResponse.java b/core/java/android/service/credentials/BeginCreateCredentialResponse.java
index cd53cb6..df93433 100644
--- a/core/java/android/service/credentials/BeginCreateCredentialResponse.java
+++ b/core/java/android/service/credentials/BeginCreateCredentialResponse.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.content.pm.ParceledListSlice;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -33,7 +34,7 @@
  * Response to a {@link BeginCreateCredentialRequest}.
  */
 public final class BeginCreateCredentialResponse implements Parcelable {
-    private final @NonNull List<CreateEntry> mCreateEntries;
+    private final @NonNull ParceledListSlice<CreateEntry> mCreateEntries;
     private final @Nullable RemoteEntry mRemoteCreateEntry;
 
     /**
@@ -41,19 +42,19 @@
      * to return.
      */
     public BeginCreateCredentialResponse() {
-        this(/*createEntries=*/new ArrayList<>(), /*remoteCreateEntry=*/null);
+        this(/*createEntries=*/new ParceledListSlice<>(new ArrayList<>()),
+                /*remoteCreateEntry=*/null);
     }
 
     private BeginCreateCredentialResponse(@NonNull Parcel in) {
-        List<CreateEntry> createEntries = new ArrayList<>();
-        in.readTypedList(createEntries, CreateEntry.CREATOR);
-        mCreateEntries = createEntries;
+        mCreateEntries = in.readParcelable(
+                null, android.content.pm.ParceledListSlice.class);
         mRemoteCreateEntry = in.readTypedObject(RemoteEntry.CREATOR);
     }
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeTypedList(mCreateEntries);
+        dest.writeParcelable(mCreateEntries, flags);
         dest.writeTypedObject(mRemoteCreateEntry, flags);
     }
 
@@ -76,7 +77,7 @@
             };
 
     /* package-private */ BeginCreateCredentialResponse(
-            @NonNull List<CreateEntry> createEntries,
+            @NonNull ParceledListSlice<CreateEntry> createEntries,
             @Nullable RemoteEntry remoteCreateEntry) {
         this.mCreateEntries = createEntries;
         com.android.internal.util.AnnotationValidations.validate(
@@ -86,7 +87,7 @@
 
     /** Returns the list of create entries to be displayed on the UI. */
     public @NonNull List<CreateEntry> getCreateEntries() {
-        return mCreateEntries;
+        return mCreateEntries.getList();
     }
 
     /** Returns the remote create entry to be displayed on the UI. */
@@ -159,7 +160,9 @@
          * Builds a new instance of {@link BeginCreateCredentialResponse}.
          */
         public @NonNull BeginCreateCredentialResponse build() {
-            return new BeginCreateCredentialResponse(mCreateEntries, mRemoteCreateEntry);
+            return new BeginCreateCredentialResponse(
+                    new ParceledListSlice<>(mCreateEntries),
+                    mRemoteCreateEntry);
         }
     }
 }
diff --git a/core/java/android/service/credentials/BeginGetCredentialResponse.java b/core/java/android/service/credentials/BeginGetCredentialResponse.java
index e25b686..5ed06ac 100644
--- a/core/java/android/service/credentials/BeginGetCredentialResponse.java
+++ b/core/java/android/service/credentials/BeginGetCredentialResponse.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.content.pm.ParceledListSlice;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -35,13 +36,13 @@
  */
 public final class BeginGetCredentialResponse implements Parcelable {
     /** List of credential entries to be displayed on the UI. */
-    private final @NonNull List<CredentialEntry> mCredentialEntries;
+    private final @NonNull ParceledListSlice<CredentialEntry> mCredentialEntries;
 
     /** List of authentication entries to be displayed on the UI. */
-    private final @NonNull List<Action> mAuthenticationEntries;
+    private final @NonNull ParceledListSlice<Action> mAuthenticationEntries;
 
     /** List of provider actions to be displayed on the UI. */
-    private final @NonNull List<Action> mActions;
+    private final @NonNull ParceledListSlice<Action> mActions;
 
     /** Remote credential entry to get the response from a different device. */
     private final @Nullable RemoteEntry mRemoteCredentialEntry;
@@ -51,31 +52,30 @@
      * or {@link Action} to return.
      */
     public BeginGetCredentialResponse() {
-        this(/*credentialEntries=*/new ArrayList<>(),
-                /*authenticationActions=*/new ArrayList<>(),
-                /*actions=*/new ArrayList<>(),
+        this(/*credentialEntries=*/new ParceledListSlice<>(new ArrayList<>()),
+                /*authenticationEntries=*/new ParceledListSlice<>(new ArrayList<>()),
+                /*actions=*/new ParceledListSlice<>(new ArrayList<>()),
                 /*remoteCredentialEntry=*/null);
     }
 
-    private BeginGetCredentialResponse(@NonNull List<CredentialEntry> credentialEntries,
-            @NonNull List<Action> authenticationEntries, @NonNull List<Action> actions,
+    private BeginGetCredentialResponse(
+            @NonNull ParceledListSlice<CredentialEntry> credentialEntries,
+            @NonNull ParceledListSlice<Action> authenticationEntries,
+            @NonNull ParceledListSlice<Action> actions,
             @Nullable RemoteEntry remoteCredentialEntry) {
-        mCredentialEntries = new ArrayList<>(credentialEntries);
-        mAuthenticationEntries = new ArrayList<>(authenticationEntries);
-        mActions = new ArrayList<>(actions);
+        mCredentialEntries = credentialEntries;
+        mAuthenticationEntries = authenticationEntries;
+        mActions = actions;
         mRemoteCredentialEntry = remoteCredentialEntry;
     }
 
     private BeginGetCredentialResponse(@NonNull Parcel in) {
-        List<CredentialEntry> credentialEntries = new ArrayList<>();
-        in.readTypedList(credentialEntries, CredentialEntry.CREATOR);
-        mCredentialEntries = credentialEntries;
-        List<Action> authenticationEntries = new ArrayList<>();
-        in.readTypedList(authenticationEntries, Action.CREATOR);
-        mAuthenticationEntries = authenticationEntries;
-        List<Action> actions = new ArrayList<>();
-        in.readTypedList(actions, Action.CREATOR);
-        mActions = actions;
+        mCredentialEntries = in.readParcelable(
+                null, android.content.pm.ParceledListSlice.class);
+        mAuthenticationEntries = in.readParcelable(
+                null, android.content.pm.ParceledListSlice.class);
+        mActions = in.readParcelable(
+                null, android.content.pm.ParceledListSlice.class);
         mRemoteCredentialEntry = in.readTypedObject(RemoteEntry.CREATOR);
     }
 
@@ -99,9 +99,9 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeTypedList(mCredentialEntries, flags);
-        dest.writeTypedList(mAuthenticationEntries, flags);
-        dest.writeTypedList(mActions, flags);
+        dest.writeParcelable(mCredentialEntries, flags);
+        dest.writeParcelable(mAuthenticationEntries, flags);
+        dest.writeParcelable(mActions, flags);
         dest.writeTypedObject(mRemoteCredentialEntry, flags);
     }
 
@@ -109,21 +109,22 @@
      * Returns the list of credential entries to be displayed on the UI.
      */
     public @NonNull List<CredentialEntry> getCredentialEntries() {
-        return mCredentialEntries;
+        return mCredentialEntries.getList();
     }
 
     /**
      * Returns the list of authentication entries to be displayed on the UI.
      */
     public @NonNull List<Action> getAuthenticationActions() {
-        return mAuthenticationEntries;
+        return mAuthenticationEntries.getList();
     }
 
     /**
      * Returns the list of actions to be displayed on the UI.
      */
     public @NonNull List<Action> getActions() {
-        return mActions;
+
+        return mActions.getList();
     }
 
     /**
@@ -268,8 +269,11 @@
          * Builds a {@link BeginGetCredentialResponse} instance.
          */
         public @NonNull BeginGetCredentialResponse build() {
-            return new BeginGetCredentialResponse(mCredentialEntries, mAuthenticationEntries,
-                    mActions, mRemoteCredentialEntry);
+            return new BeginGetCredentialResponse(
+                    new ParceledListSlice<>(mCredentialEntries),
+                    new ParceledListSlice<>(mAuthenticationEntries),
+                            new ParceledListSlice<>(mActions),
+                    mRemoteCredentialEntry);
         }
     }
 }
diff --git a/core/java/android/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
index d2a4a66..fb2f4ad 100644
--- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java
+++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
@@ -42,7 +42,6 @@
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.util.Slog;
 import android.util.Xml;
 
@@ -54,7 +53,6 @@
 
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -181,7 +179,8 @@
         if (disableSystemAppVerificationForTests) {
             Bundle metadata = serviceInfo.metaData;
             if (metadata == null) {
-                Slog.e(TAG, "isValidSystemProvider - metadata is null: " + serviceInfo);
+                Slog.w(TAG, "metadata is null while reading "
+                        + "TEST_SYSTEM_PROVIDER_META_DATA_KEY: " + serviceInfo);
                 return false;
             }
             return metadata.getBoolean(
@@ -200,7 +199,7 @@
         // 1. Get the metadata for the service.
         final Bundle metadata = serviceInfo.metaData;
         if (metadata == null) {
-            Log.i(TAG, "populateMetadata - metadata is null");
+            Slog.w(TAG, "Metadata is null for provider: " + serviceInfo.getComponentName());
             return builder;
         }
 
@@ -209,12 +208,13 @@
         try {
             resources = pm.getResourcesForApplication(serviceInfo.applicationInfo);
         } catch (PackageManager.NameNotFoundException e) {
-            Log.e(TAG, "Failed to get app resources", e);
+            Slog.e(TAG, "Failed to get app resources", e);
         }
 
         // 3. Stop if we are missing data.
-        if (metadata == null || resources == null) {
-            Log.i(TAG, "populateMetadata - resources is null");
+        if (resources == null) {
+            Slog.w(TAG, "Resources are null for the serviceInfo being processed: "
+                    + serviceInfo.getComponentName());
             return builder;
         }
 
@@ -222,7 +222,7 @@
         try {
             builder = extractXmlMetadata(context, builder, serviceInfo, pm, resources);
         } catch (Exception e) {
-            Log.e(TAG, "Failed to get XML metadata", e);
+            Slog.e(TAG, "Failed to get XML metadata", e);
         }
 
         return builder;
@@ -259,7 +259,7 @@
                             afsAttributes.getString(
                                     R.styleable.CredentialProvider_settingsSubtitle));
                 } catch (Exception e) {
-                    Log.e(TAG, "Failed to get XML attr", e);
+                    Slog.e(TAG, "Failed to get XML attr", e);
                 } finally {
                     if (afsAttributes != null) {
                         afsAttributes.recycle();
@@ -267,10 +267,10 @@
                 }
                 builder.addCapabilities(parseXmlProviderOuterCapabilities(parser, resources));
             } else {
-                Log.e(TAG, "Meta-data does not start with credential-provider-service tag");
+                Slog.w(TAG, "Meta-data does not start with credential-provider-service tag");
             }
         } catch (IOException | XmlPullParserException e) {
-            Log.e(TAG, "Error parsing credential provider service meta-data", e);
+            Slog.e(TAG, "Error parsing credential provider service meta-data", e);
         }
 
         return builder;
@@ -329,7 +329,7 @@
                 return si;
             }
         } catch (RemoteException e) {
-            Slog.v(TAG, e.getMessage());
+            Slog.e(TAG, "Unable to get serviceInfo", e);
         }
         throw new PackageManager.NameNotFoundException(serviceComponent.toString());
     }
@@ -377,10 +377,8 @@
                 }
 
                 services.add(serviceInfo);
-            } catch (SecurityException e) {
-                Slog.e(TAG, "Error getting info for " + serviceInfo + ": " + e);
-            } catch (PackageManager.NameNotFoundException e) {
-                Slog.e(TAG, "Error getting info for " + serviceInfo + ": " + e);
+            } catch (SecurityException | PackageManager.NameNotFoundException e) {
+                Slog.e(TAG, "Error getting info for " + serviceInfo, e);
             }
         }
         return services;
@@ -432,7 +430,7 @@
             return pp;
         } catch (SecurityException e) {
             // If the current user is not enrolled in DPM then this can throw a security error.
-            Log.e(TAG, "Failed to get device policy: " + e);
+            Slog.e(TAG, "Failed to get device policy: " + e);
         }
 
         return null;
@@ -593,7 +591,7 @@
         for (ResolveInfo resolveInfo : resolveInfos) {
             final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
             if (serviceInfo == null) {
-                Log.i(TAG, "No serviceInfo found for resolveInfo so skipping this provider");
+                Slog.d(TAG, "No serviceInfo found for resolveInfo, so skipping provider");
                 continue;
             }
 
@@ -608,10 +606,8 @@
                 if (!cpi.isSystemProvider()) {
                     services.add(cpi);
                 }
-            } catch (SecurityException e) {
-                Slog.e(TAG, "Error getting info for " + serviceInfo + ": " + e);
             } catch (Exception e) {
-                Slog.e(TAG, "Error getting info for " + serviceInfo + ": " + e);
+                Slog.e(TAG, "Error getting info for " + serviceInfo, e);
             }
         }
         return services;
diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java
index e87333f..53a5fd5 100644
--- a/core/java/android/service/credentials/CredentialProviderService.java
+++ b/core/java/android/service/credentials/CredentialProviderService.java
@@ -226,7 +226,7 @@
         if (SERVICE_INTERFACE.equals(intent.getAction())) {
             return mInterface.asBinder();
         }
-        Log.i(TAG, "Failed to bind with intent: " + intent);
+        Log.d(TAG, "Failed to bind with intent: " + intent);
         return null;
     }
 
diff --git a/core/java/android/service/dreams/DreamManagerInternal.java b/core/java/android/service/dreams/DreamManagerInternal.java
index dd5373f..82571db 100644
--- a/core/java/android/service/dreams/DreamManagerInternal.java
+++ b/core/java/android/service/dreams/DreamManagerInternal.java
@@ -80,10 +80,10 @@
      */
     public interface DreamManagerStateListener {
         /**
-         * Called when keep dreaming when undocked has changed.
+         * Called when keep dreaming when plug has changed.
          *
          * @param keepDreaming True if the current dream should continue when undocking.
          */
-        void onKeepDreamingWhenUndockedChanged(boolean keepDreaming);
+        void onKeepDreamingWhenUnpluggingChanged(boolean keepDreaming);
     }
 }
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 402da28..828c062 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -45,7 +45,6 @@
 import android.text.TextUtils;
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.PluralsMessageFormatter;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
@@ -59,7 +58,6 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Calendar;
 import java.util.Date;
@@ -310,86 +308,6 @@
         return buffer.toString();
     }
 
-    public Diff diff(ZenModeConfig to) {
-        final Diff d = new Diff();
-        if (to == null) {
-            return d.addLine("config", "delete");
-        }
-        if (user != to.user) {
-            d.addLine("user", user, to.user);
-        }
-        if (allowAlarms != to.allowAlarms) {
-            d.addLine("allowAlarms", allowAlarms, to.allowAlarms);
-        }
-        if (allowMedia != to.allowMedia) {
-            d.addLine("allowMedia", allowMedia, to.allowMedia);
-        }
-        if (allowSystem != to.allowSystem) {
-            d.addLine("allowSystem", allowSystem, to.allowSystem);
-        }
-        if (allowCalls != to.allowCalls) {
-            d.addLine("allowCalls", allowCalls, to.allowCalls);
-        }
-        if (allowReminders != to.allowReminders) {
-            d.addLine("allowReminders", allowReminders, to.allowReminders);
-        }
-        if (allowEvents != to.allowEvents) {
-            d.addLine("allowEvents", allowEvents, to.allowEvents);
-        }
-        if (allowRepeatCallers != to.allowRepeatCallers) {
-            d.addLine("allowRepeatCallers", allowRepeatCallers, to.allowRepeatCallers);
-        }
-        if (allowMessages != to.allowMessages) {
-            d.addLine("allowMessages", allowMessages, to.allowMessages);
-        }
-        if (allowCallsFrom != to.allowCallsFrom) {
-            d.addLine("allowCallsFrom", allowCallsFrom, to.allowCallsFrom);
-        }
-        if (allowMessagesFrom != to.allowMessagesFrom) {
-            d.addLine("allowMessagesFrom", allowMessagesFrom, to.allowMessagesFrom);
-        }
-        if (suppressedVisualEffects != to.suppressedVisualEffects) {
-            d.addLine("suppressedVisualEffects", suppressedVisualEffects,
-                    to.suppressedVisualEffects);
-        }
-        final ArraySet<String> allRules = new ArraySet<>();
-        addKeys(allRules, automaticRules);
-        addKeys(allRules, to.automaticRules);
-        final int N = allRules.size();
-        for (int i = 0; i < N; i++) {
-            final String rule = allRules.valueAt(i);
-            final ZenRule fromRule = automaticRules != null ? automaticRules.get(rule) : null;
-            final ZenRule toRule = to.automaticRules != null ? to.automaticRules.get(rule) : null;
-            ZenRule.appendDiff(d, "automaticRule[" + rule + "]", fromRule, toRule);
-        }
-        ZenRule.appendDiff(d, "manualRule", manualRule, to.manualRule);
-
-        if (areChannelsBypassingDnd != to.areChannelsBypassingDnd) {
-            d.addLine("areChannelsBypassingDnd", areChannelsBypassingDnd,
-                    to.areChannelsBypassingDnd);
-        }
-        return d;
-    }
-
-    public static Diff diff(ZenModeConfig from, ZenModeConfig to) {
-        if (from == null) {
-            final Diff d = new Diff();
-            if (to != null) {
-                d.addLine("config", "insert");
-            }
-            return d;
-        }
-        return from.diff(to);
-    }
-
-    private static <T> void addKeys(ArraySet<T> set, ArrayMap<T, ?> map) {
-        if (map != null) {
-            for (int i = 0; i < map.size(); i++) {
-                set.add(map.keyAt(i));
-            }
-        }
-    }
-
     public boolean isValid() {
         if (!isValidManualRule(manualRule)) return false;
         final int N = automaticRules.size();
@@ -1922,66 +1840,6 @@
             proto.end(token);
         }
 
-        private static void appendDiff(Diff d, String item, ZenRule from, ZenRule to) {
-            if (d == null) return;
-            if (from == null) {
-                if (to != null) {
-                    d.addLine(item, "insert");
-                }
-                return;
-            }
-            from.appendDiff(d, item, to);
-        }
-
-        private void appendDiff(Diff d, String item, ZenRule to) {
-            if (to == null) {
-                d.addLine(item, "delete");
-                return;
-            }
-            if (enabled != to.enabled) {
-                d.addLine(item, "enabled", enabled, to.enabled);
-            }
-            if (snoozing != to.snoozing) {
-                d.addLine(item, "snoozing", snoozing, to.snoozing);
-            }
-            if (!Objects.equals(name, to.name)) {
-                d.addLine(item, "name", name, to.name);
-            }
-            if (zenMode != to.zenMode) {
-                d.addLine(item, "zenMode", zenMode, to.zenMode);
-            }
-            if (!Objects.equals(conditionId, to.conditionId)) {
-                d.addLine(item, "conditionId", conditionId, to.conditionId);
-            }
-            if (!Objects.equals(condition, to.condition)) {
-                d.addLine(item, "condition", condition, to.condition);
-            }
-            if (!Objects.equals(component, to.component)) {
-                d.addLine(item, "component", component, to.component);
-            }
-            if (!Objects.equals(configurationActivity, to.configurationActivity)) {
-                d.addLine(item, "configActivity", configurationActivity, to.configurationActivity);
-            }
-            if (!Objects.equals(id, to.id)) {
-                d.addLine(item, "id", id, to.id);
-            }
-            if (creationTime != to.creationTime) {
-                d.addLine(item, "creationTime", creationTime, to.creationTime);
-            }
-            if (!Objects.equals(enabler, to.enabler)) {
-                d.addLine(item, "enabler", enabler, to.enabler);
-            }
-            if (!Objects.equals(zenPolicy, to.zenPolicy)) {
-                d.addLine(item, "zenPolicy", zenPolicy, to.zenPolicy);
-            }
-            if (modified != to.modified) {
-                d.addLine(item, "modified", modified, to.modified);
-            }
-            if (!Objects.equals(pkg, to.pkg)) {
-                d.addLine(item, "pkg", pkg, to.pkg);
-            }
-        }
-
         @Override
         public boolean equals(@Nullable Object o) {
             if (!(o instanceof ZenRule)) return false;
@@ -2040,40 +1898,6 @@
         };
     }
 
-    public static class Diff {
-        private final ArrayList<String> lines = new ArrayList<>();
-
-        @Override
-        public String toString() {
-            final StringBuilder sb = new StringBuilder("Diff[");
-            final int N = lines.size();
-            for (int i = 0; i < N; i++) {
-                if (i > 0) {
-                    sb.append(",\n");
-                }
-                sb.append(lines.get(i));
-            }
-            return sb.append(']').toString();
-        }
-
-        private Diff addLine(String item, String action) {
-            lines.add(item + ":" + action);
-            return this;
-        }
-
-        public Diff addLine(String item, String subitem, Object from, Object to) {
-            return addLine(item + "." + subitem, from, to);
-        }
-
-        public Diff addLine(String item, Object from, Object to) {
-            return addLine(item, from + "->" + to);
-        }
-
-        public boolean isEmpty() {
-            return lines.isEmpty();
-        }
-    }
-
     /**
      * Determines whether dnd behavior should mute all ringer-controlled sounds
      * This includes notification, ringer and system sounds
diff --git a/core/java/android/service/notification/ZenModeDiff.java b/core/java/android/service/notification/ZenModeDiff.java
new file mode 100644
index 0000000..c7b89eb
--- /dev/null
+++ b/core/java/android/service/notification/ZenModeDiff.java
@@ -0,0 +1,542 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.notification;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * ZenModeDiff is a utility class meant to encapsulate the diff between ZenModeConfigs and their
+ * subcomponents (automatic and manual ZenRules).
+ * @hide
+ */
+public class ZenModeDiff {
+    /**
+     * Enum representing whether the existence of a config or rule has changed (added or removed,
+     * or "none" meaning there is no change, which may either mean both null, or there exists a
+     * diff in fields rather than add/remove).
+     */
+    @IntDef(value = {
+            NONE,
+            ADDED,
+            REMOVED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ExistenceChange{}
+
+    public static final int NONE = 0;
+    public static final int ADDED = 1;
+    public static final int REMOVED = 2;
+
+    /**
+     * Diff class representing an individual field diff.
+     * @param <T> The type of the field.
+     */
+    public static class FieldDiff<T> {
+        private final T mFrom;
+        private final T mTo;
+
+        /**
+         * Constructor to create a FieldDiff object with the given values.
+         * @param from from (old) value
+         * @param to to (new) value
+         */
+        public FieldDiff(@Nullable T from, @Nullable T to) {
+            mFrom = from;
+            mTo = to;
+        }
+
+        /**
+         * Get the "from" value
+         */
+        public T from() {
+            return mFrom;
+        }
+
+        /**
+         * Get the "to" value
+         */
+        public T to() {
+            return mTo;
+        }
+
+        /**
+         * Get the string representation of this field diff, in the form of "from->to".
+         */
+        @Override
+        public String toString() {
+            return mFrom + "->" + mTo;
+        }
+
+        /**
+         * Returns whether this represents an actual diff.
+         */
+        public boolean hasDiff() {
+            // note that Objects.equals handles null values gracefully.
+            return !Objects.equals(mFrom, mTo);
+        }
+    }
+
+    /**
+     * Base diff class that contains info about whether something was added, and a set of named
+     * fields that changed.
+     * Extend for diffs of specific types of objects.
+     */
+    private abstract static class BaseDiff {
+        // Whether the diff was added or removed
+        @ExistenceChange private int mExists = NONE;
+
+        // Map from field name to diffs for any standalone fields in the object.
+        private ArrayMap<String, FieldDiff> mFields = new ArrayMap<>();
+
+        // Functions for actually diffing objects and string representations have to be implemented
+        // by subclasses.
+
+        /**
+         * Return whether this diff represents any changes.
+         */
+        public abstract boolean hasDiff();
+
+        /**
+         * Return a string representation of the diff.
+         */
+        public abstract String toString();
+
+        /**
+         * Constructor that takes the two objects meant to be compared. This constructor sets
+         * whether there is an existence change (added or removed).
+         * @param from previous Object
+         * @param to new Object
+         */
+        BaseDiff(Object from, Object to) {
+            if (from == null) {
+                if (to != null) {
+                    mExists = ADDED;
+                }
+                // If both are null, there isn't an existence change; callers/inheritors must handle
+                // the both null case.
+            } else if (to == null) {
+                // in this case, we know that from != null
+                mExists = REMOVED;
+            }
+
+            // Subclasses should implement the actual diffing functionality in their own
+            // constructors.
+        }
+
+        /**
+         * Add a diff for a specific field to the map.
+         * @param name field name
+         * @param diff FieldDiff object representing the diff
+         */
+        final void addField(String name, FieldDiff diff) {
+            mFields.put(name, diff);
+        }
+
+        /**
+         * Returns whether this diff represents a config being newly added.
+         */
+        public final boolean wasAdded() {
+            return mExists == ADDED;
+        }
+
+        /**
+         * Returns whether this diff represents a config being removed.
+         */
+        public final boolean wasRemoved() {
+            return mExists == REMOVED;
+        }
+
+        /**
+         * Returns whether this diff represents an object being either added or removed.
+         */
+        public final boolean hasExistenceChange() {
+            return mExists != NONE;
+        }
+
+        /**
+         * Returns whether there are any individual field diffs.
+         */
+        public final boolean hasFieldDiffs() {
+            return mFields.size() > 0;
+        }
+
+        /**
+         * Returns the diff for the specific named field if it exists
+         */
+        public final FieldDiff getDiffForField(String name) {
+            return mFields.getOrDefault(name, null);
+        }
+
+        /**
+         * Get the set of all field names with some diff.
+         */
+        public final Set<String> fieldNamesWithDiff() {
+            return mFields.keySet();
+        }
+    }
+
+    /**
+     * Diff class representing a diff between two ZenModeConfigs.
+     */
+    public static class ConfigDiff extends BaseDiff {
+        // Rules. Automatic rule map is keyed by the rule name.
+        private final ArrayMap<String, RuleDiff> mAutomaticRulesDiff = new ArrayMap<>();
+        private RuleDiff mManualRuleDiff;
+
+        // Helpers for string generation
+        private static final String ALLOW_CALLS_FROM_FIELD = "allowCallsFrom";
+        private static final String ALLOW_MESSAGES_FROM_FIELD = "allowMessagesFrom";
+        private static final String ALLOW_CONVERSATIONS_FROM_FIELD = "allowConversationsFrom";
+        private static final Set<String> PEOPLE_TYPE_FIELDS =
+                Set.of(ALLOW_CALLS_FROM_FIELD, ALLOW_MESSAGES_FROM_FIELD);
+
+        /**
+         * Create a diff that contains diffs between the "from" and "to" ZenModeConfigs.
+         *
+         * @param from previous ZenModeConfig
+         * @param to   new ZenModeConfig
+         */
+        public ConfigDiff(ZenModeConfig from, ZenModeConfig to) {
+            super(from, to);
+            // If both are null skip
+            if (from == null && to == null) {
+                return;
+            }
+            if (hasExistenceChange()) {
+                // either added or removed; return here. otherwise (they're not both null) there's
+                // field diffs.
+                return;
+            }
+
+            // Now we compare all the fields, knowing there's a diff and that neither is null
+            if (from.user != to.user) {
+                addField("user", new FieldDiff<>(from.user, to.user));
+            }
+            if (from.allowAlarms != to.allowAlarms) {
+                addField("allowAlarms", new FieldDiff<>(from.allowAlarms, to.allowAlarms));
+            }
+            if (from.allowMedia != to.allowMedia) {
+                addField("allowMedia", new FieldDiff<>(from.allowMedia, to.allowMedia));
+            }
+            if (from.allowSystem != to.allowSystem) {
+                addField("allowSystem", new FieldDiff<>(from.allowSystem, to.allowSystem));
+            }
+            if (from.allowCalls != to.allowCalls) {
+                addField("allowCalls", new FieldDiff<>(from.allowCalls, to.allowCalls));
+            }
+            if (from.allowReminders != to.allowReminders) {
+                addField("allowReminders",
+                        new FieldDiff<>(from.allowReminders, to.allowReminders));
+            }
+            if (from.allowEvents != to.allowEvents) {
+                addField("allowEvents", new FieldDiff<>(from.allowEvents, to.allowEvents));
+            }
+            if (from.allowRepeatCallers != to.allowRepeatCallers) {
+                addField("allowRepeatCallers",
+                        new FieldDiff<>(from.allowRepeatCallers, to.allowRepeatCallers));
+            }
+            if (from.allowMessages != to.allowMessages) {
+                addField("allowMessages",
+                        new FieldDiff<>(from.allowMessages, to.allowMessages));
+            }
+            if (from.allowConversations != to.allowConversations) {
+                addField("allowConversations",
+                        new FieldDiff<>(from.allowConversations, to.allowConversations));
+            }
+            if (from.allowCallsFrom != to.allowCallsFrom) {
+                addField("allowCallsFrom",
+                        new FieldDiff<>(from.allowCallsFrom, to.allowCallsFrom));
+            }
+            if (from.allowMessagesFrom != to.allowMessagesFrom) {
+                addField("allowMessagesFrom",
+                        new FieldDiff<>(from.allowMessagesFrom, to.allowMessagesFrom));
+            }
+            if (from.allowConversationsFrom != to.allowConversationsFrom) {
+                addField("allowConversationsFrom",
+                        new FieldDiff<>(from.allowConversationsFrom, to.allowConversationsFrom));
+            }
+            if (from.suppressedVisualEffects != to.suppressedVisualEffects) {
+                addField("suppressedVisualEffects",
+                        new FieldDiff<>(from.suppressedVisualEffects, to.suppressedVisualEffects));
+            }
+            if (from.areChannelsBypassingDnd != to.areChannelsBypassingDnd) {
+                addField("areChannelsBypassingDnd",
+                        new FieldDiff<>(from.areChannelsBypassingDnd, to.areChannelsBypassingDnd));
+            }
+
+            // Compare automatic and manual rules
+            final ArraySet<String> allRules = new ArraySet<>();
+            addKeys(allRules, from.automaticRules);
+            addKeys(allRules, to.automaticRules);
+            final int num = allRules.size();
+            for (int i = 0; i < num; i++) {
+                final String rule = allRules.valueAt(i);
+                final ZenModeConfig.ZenRule
+                        fromRule = from.automaticRules != null ? from.automaticRules.get(rule)
+                        : null;
+                final ZenModeConfig.ZenRule
+                        toRule = to.automaticRules != null ? to.automaticRules.get(rule) : null;
+                RuleDiff ruleDiff = new RuleDiff(fromRule, toRule);
+                if (ruleDiff.hasDiff()) {
+                    mAutomaticRulesDiff.put(rule, ruleDiff);
+                }
+            }
+            // If there's no diff this may turn out to be null, but that's also fine
+            RuleDiff manualRuleDiff = new RuleDiff(from.manualRule, to.manualRule);
+            if (manualRuleDiff.hasDiff()) {
+                mManualRuleDiff = manualRuleDiff;
+            }
+        }
+
+        private static <T> void addKeys(ArraySet<T> set, ArrayMap<T, ?> map) {
+            if (map != null) {
+                for (int i = 0; i < map.size(); i++) {
+                    set.add(map.keyAt(i));
+                }
+            }
+        }
+
+        /**
+         * Returns whether this diff object contains any diffs in any field.
+         */
+        @Override
+        public boolean hasDiff() {
+            return hasExistenceChange()
+                    || hasFieldDiffs()
+                    || mManualRuleDiff != null
+                    || mAutomaticRulesDiff.size() > 0;
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder sb = new StringBuilder("Diff[");
+            if (!hasDiff()) {
+                sb.append("no changes");
+            }
+
+            // If added or deleted, then that's just the end of it
+            if (hasExistenceChange()) {
+                if (wasAdded()) {
+                    sb.append("added");
+                } else if (wasRemoved()) {
+                    sb.append("removed");
+                }
+            }
+
+            // Handle top-level field change
+            boolean first = true;
+            for (String key : fieldNamesWithDiff()) {
+                FieldDiff diff = getDiffForField(key);
+                if (diff == null) {
+                    // this shouldn't happen, but
+                    continue;
+                }
+                if (first) {
+                    first = false;
+                } else {
+                    sb.append(",\n");
+                }
+
+                // Some special handling for people- and conversation-type fields for readability
+                if (PEOPLE_TYPE_FIELDS.contains(key)) {
+                    sb.append(key);
+                    sb.append(":");
+                    sb.append(ZenModeConfig.sourceToString((int) diff.from()));
+                    sb.append("->");
+                    sb.append(ZenModeConfig.sourceToString((int) diff.to()));
+                } else if (key.equals(ALLOW_CONVERSATIONS_FROM_FIELD)) {
+                    sb.append(key);
+                    sb.append(":");
+                    sb.append(ZenPolicy.conversationTypeToString((int) diff.from()));
+                    sb.append("->");
+                    sb.append(ZenPolicy.conversationTypeToString((int) diff.to()));
+                } else {
+                    sb.append(key);
+                    sb.append(":");
+                    sb.append(diff);
+                }
+            }
+
+            // manual rule
+            if (mManualRuleDiff != null && mManualRuleDiff.hasDiff()) {
+                if (first) {
+                    first = false;
+                } else {
+                    sb.append(",\n");
+                }
+                sb.append("manualRule:");
+                sb.append(mManualRuleDiff);
+            }
+
+            // automatic rules
+            for (String rule : mAutomaticRulesDiff.keySet()) {
+                RuleDiff diff = mAutomaticRulesDiff.get(rule);
+                if (diff != null && diff.hasDiff()) {
+                    if (first) {
+                        first = false;
+                    } else {
+                        sb.append(",\n");
+                    }
+                    sb.append("automaticRule[");
+                    sb.append(rule);
+                    sb.append("]:");
+                    sb.append(diff);
+                }
+            }
+
+            return sb.append(']').toString();
+        }
+
+        /**
+         * Get the diff in manual rule, if it exists.
+         */
+        public RuleDiff getManualRuleDiff() {
+            return mManualRuleDiff;
+        }
+
+        /**
+         * Get the full map of automatic rule diffs, or null if there are no diffs.
+         */
+        public ArrayMap<String, RuleDiff> getAllAutomaticRuleDiffs() {
+            return (mAutomaticRulesDiff.size() > 0) ? mAutomaticRulesDiff : null;
+        }
+    }
+
+    /**
+     * Diff class representing a change between two ZenRules.
+     */
+    public static class RuleDiff extends BaseDiff {
+        /**
+         * Create a RuleDiff representing the difference between two ZenRule objects.
+         * @param from previous ZenRule
+         * @param to new ZenRule
+         * @return The diff between the two given ZenRules
+         */
+        public RuleDiff(ZenModeConfig.ZenRule from, ZenModeConfig.ZenRule to) {
+            super(from, to);
+            // Short-circuit the both-null case
+            if (from == null && to == null) {
+                return;
+            }
+            // Return if the diff was added or removed
+            if (hasExistenceChange()) {
+                return;
+            }
+
+            if (from.enabled != to.enabled) {
+                addField("enabled", new FieldDiff<>(from.enabled, to.enabled));
+            }
+            if (from.snoozing != to.snoozing) {
+                addField("snoozing", new FieldDiff<>(from.snoozing, to.snoozing));
+            }
+            if (!Objects.equals(from.name, to.name)) {
+                addField("name", new FieldDiff<>(from.name, to.name));
+            }
+            if (from.zenMode != to.zenMode) {
+                addField("zenMode", new FieldDiff<>(from.zenMode, to.zenMode));
+            }
+            if (!Objects.equals(from.conditionId, to.conditionId)) {
+                addField("conditionId", new FieldDiff<>(from.conditionId, to.conditionId));
+            }
+            if (!Objects.equals(from.condition, to.condition)) {
+                addField("condition", new FieldDiff<>(from.condition, to.condition));
+            }
+            if (!Objects.equals(from.component, to.component)) {
+                addField("component", new FieldDiff<>(from.component, to.component));
+            }
+            if (!Objects.equals(from.configurationActivity, to.configurationActivity)) {
+                addField("configurationActivity", new FieldDiff<>(
+                        from.configurationActivity, to.configurationActivity));
+            }
+            if (!Objects.equals(from.id, to.id)) {
+                addField("id", new FieldDiff<>(from.id, to.id));
+            }
+            if (from.creationTime != to.creationTime) {
+                addField("creationTime",
+                        new FieldDiff<>(from.creationTime, to.creationTime));
+            }
+            if (!Objects.equals(from.enabler, to.enabler)) {
+                addField("enabler", new FieldDiff<>(from.enabler, to.enabler));
+            }
+            if (!Objects.equals(from.zenPolicy, to.zenPolicy)) {
+                addField("zenPolicy", new FieldDiff<>(from.zenPolicy, to.zenPolicy));
+            }
+            if (from.modified != to.modified) {
+                addField("modified", new FieldDiff<>(from.modified, to.modified));
+            }
+            if (!Objects.equals(from.pkg, to.pkg)) {
+                addField("pkg", new FieldDiff<>(from.pkg, to.pkg));
+            }
+        }
+
+        /**
+         * Returns whether this object represents an actual diff.
+         */
+        @Override
+        public boolean hasDiff() {
+            return hasExistenceChange() || hasFieldDiffs();
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder sb = new StringBuilder("ZenRuleDiff{");
+            // If there's no diff, probably we haven't actually let this object continue existing
+            // but might as well handle this case.
+            if (!hasDiff()) {
+                sb.append("no changes");
+            }
+
+            // If added or deleted, then that's just the end of it
+            if (hasExistenceChange()) {
+                if (wasAdded()) {
+                    sb.append("added");
+                } else if (wasRemoved()) {
+                    sb.append("removed");
+                }
+            }
+
+            // Go through all of the individual fields
+            boolean first = true;
+            for (String key : fieldNamesWithDiff()) {
+                FieldDiff diff = getDiffForField(key);
+                if (diff == null) {
+                    // this shouldn't happen, but
+                    continue;
+                }
+                if (first) {
+                    first = false;
+                } else {
+                    sb.append(", ");
+                }
+
+                sb.append(key);
+                sb.append(":");
+                sb.append(diff);
+            }
+
+            return sb.append("}").toString();
+        }
+    }
+}
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 8688a18..24c96ea 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -1573,16 +1573,6 @@
         }
 
         @Override
-        public void onError(int status) {
-            Slog.i(TAG, "onError: " + status);
-            // TODO(b/271534248): This is a workaround before the sound trigger uses the new error
-            // method.
-            Message.obtain(mHandler, MSG_DETECTION_SOUND_TRIGGER_FAILURE,
-                    new SoundTriggerFailure(SoundTriggerFailure.ERROR_CODE_UNKNOWN,
-                            "Sound trigger error")).sendToTarget();
-        }
-
-        @Override
         public void onHotwordDetectionServiceFailure(
                 HotwordDetectionServiceFailure hotwordDetectionServiceFailure) {
             Slog.v(TAG, "onHotwordDetectionServiceFailure: " + hotwordDetectionServiceFailure);
@@ -1605,6 +1595,12 @@
         }
 
         @Override
+        public void onSoundTriggerFailure(SoundTriggerFailure soundTriggerFailure) {
+            Message.obtain(mHandler, MSG_DETECTION_SOUND_TRIGGER_FAILURE,
+                    Objects.requireNonNull(soundTriggerFailure)).sendToTarget();
+        }
+
+        @Override
         public void onUnknownFailure(String errorMessage) throws RemoteException {
             Slog.v(TAG, "onUnknownFailure: " + errorMessage);
             Message.obtain(mHandler, MSG_DETECTION_UNKNOWN_FAILURE,
diff --git a/core/java/android/service/voice/SoftwareHotwordDetector.java b/core/java/android/service/voice/SoftwareHotwordDetector.java
index eac7aee..7ab4faf 100644
--- a/core/java/android/service/voice/SoftwareHotwordDetector.java
+++ b/core/java/android/service/voice/SoftwareHotwordDetector.java
@@ -234,14 +234,6 @@
         }
 
         @Override
-        public void onError(int status) throws RemoteException {
-            if (DEBUG) {
-                Slog.i(TAG, "Ignored #onError (" + status + ") event");
-            }
-            // TODO: Check if we still need to implement this method with DetectorFailure mechanism.
-        }
-
-        @Override
         public void onHotwordDetectionServiceFailure(
                 HotwordDetectionServiceFailure hotwordDetectionServiceFailure)
                 throws RemoteException {
@@ -265,6 +257,13 @@
         }
 
         @Override
+        public void onSoundTriggerFailure(SoundTriggerFailure onSoundTriggerFailure)
+                throws RemoteException {
+            // It should never be called here.
+            Slog.wtf(TAG, "Unexpected STFailure in software detector: " + onSoundTriggerFailure);
+        }
+
+        @Override
         public void onUnknownFailure(String errorMessage) throws RemoteException {
             Slog.v(TAG, "onUnknownFailure: " + errorMessage);
             Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> {
diff --git a/core/java/android/service/voice/SoundTriggerFailure.java b/core/java/android/service/voice/SoundTriggerFailure.java
index 5560800..2ce5e5d 100644
--- a/core/java/android/service/voice/SoundTriggerFailure.java
+++ b/core/java/android/service/voice/SoundTriggerFailure.java
@@ -73,18 +73,28 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface SoundTriggerErrorCode {}
 
-    private int mErrorCode = ERROR_CODE_UNKNOWN;
-    private String mErrorMessage = "Unknown";
+    private final int mErrorCode;
+    private final String mErrorMessage;
 
     /**
      * @hide
      */
     @TestApi
-    public SoundTriggerFailure(int errorCode, @NonNull String errorMessage) {
+    public SoundTriggerFailure(@SoundTriggerErrorCode int errorCode,
+            @NonNull String errorMessage) {
         if (TextUtils.isEmpty(errorMessage)) {
             throw new IllegalArgumentException("errorMessage is empty or null.");
         }
-        mErrorCode = errorCode;
+        switch (errorCode) {
+            case ERROR_CODE_UNKNOWN:
+            case ERROR_CODE_MODULE_DIED:
+            case ERROR_CODE_RECOGNITION_RESUME_FAILED:
+            case ERROR_CODE_UNEXPECTED_PREEMPTION:
+                mErrorCode = errorCode;
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid ErrorCode: " + errorCode);
+        }
         mErrorMessage = errorMessage;
     }
 
@@ -110,13 +120,14 @@
     @FailureSuggestedAction.FailureSuggestedActionDef
     public int getSuggestedAction() {
         switch (mErrorCode) {
+            case ERROR_CODE_UNKNOWN:
             case ERROR_CODE_MODULE_DIED:
             case ERROR_CODE_UNEXPECTED_PREEMPTION:
                 return FailureSuggestedAction.RECREATE_DETECTOR;
             case ERROR_CODE_RECOGNITION_RESUME_FAILED:
                 return FailureSuggestedAction.RESTART_RECOGNITION;
             default:
-                return FailureSuggestedAction.NONE;
+                throw new AssertionError("Unexpected error code");
         }
     }
 
diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java
index b4f5ff1..93b7964 100644
--- a/core/java/android/service/voice/VisualQueryDetector.java
+++ b/core/java/android/service/voice/VisualQueryDetector.java
@@ -391,12 +391,6 @@
         }
 
         @Override
-        public void onError(int status) throws RemoteException {
-            Slog.v(TAG, "Initialization Error: (" + status + ")");
-            // Do nothing
-        }
-
-        @Override
         public void onHotwordDetectionServiceFailure(
                 HotwordDetectionServiceFailure hotwordDetectionServiceFailure)
                 throws RemoteException {
@@ -420,6 +414,11 @@
         }
 
         @Override
+        public void onSoundTriggerFailure(SoundTriggerFailure soundTriggerFailure) {
+            Slog.wtf(TAG, "Unexpected STFailure in VisualQueryDetector" + soundTriggerFailure);
+        }
+
+        @Override
         public void onUnknownFailure(String errorMessage) throws RemoteException {
             Slog.v(TAG, "onUnknownFailure: " + errorMessage);
             Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> {
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index fcc64b0..68cce4a 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -50,6 +50,7 @@
 import android.util.ArraySet;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IVoiceActionCheckCallback;
 import com.android.internal.app.IVoiceInteractionManagerService;
@@ -200,6 +201,9 @@
 
     private final Set<HotwordDetector> mActiveDetectors = new ArraySet<>();
 
+    // True if any of the createAOHD methods should use the test ST module.
+    @GuardedBy("mLock")
+    private boolean mTestModuleForAlwaysOnHotwordDetectorEnabled = false;
 
     private void onDetectorRemoteException(@NonNull IBinder token, int detectorType) {
         Log.d(TAG, "onDetectorRemoteException for " + HotwordDetector.detectorTypeToString(
@@ -512,14 +516,14 @@
         Objects.requireNonNull(callback);
         return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
                 /* supportHotwordDetectionService= */ false, /* options= */ null,
-                /* sharedMemory= */ null, /* moduleProperties */ null, executor, callback);
+                /* sharedMemory= */ null, /* moduleProperties= */ null, executor, callback);
     }
 
     /**
      * Same as {@link createAlwaysOnHotwordDetector(String, Locale, Executor,
      * AlwaysOnHotwordDetector.Callback)}, but allow explicit selection of the underlying ST
      * module to attach to.
-     * Use {@link listModuleProperties} to get available modules to attach to.
+     * Use {@link #listModuleProperties()} to get available modules to attach to.
      * @hide
      */
     @TestApi
@@ -645,14 +649,14 @@
         Objects.requireNonNull(callback);
         return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
                 /* supportHotwordDetectionService= */ true, options, sharedMemory,
-                /* moduleProperties */ null, executor, callback);
+                /* moduleProperties= */ null, executor, callback);
     }
 
     /**
      * Same as {@link createAlwaysOnHotwordDetector(String, Locale,
      * PersistableBundle, SharedMemory, Executor, AlwaysOnHotwordDetector.Callback)},
      * but allow explicit selection of the underlying ST module to attach to.
-     * Use {@link listModuleProperties} to get available modules to attach to.
+     * Use {@link #listModuleProperties()} to get available modules to attach to.
      * @hide
      */
     @TestApi
@@ -717,6 +721,10 @@
 
             try {
                 dspDetector.registerOnDestroyListener(this::onHotwordDetectorDestroyed);
+                // Check if we are currently overridden, and should use the test module.
+                if (mTestModuleForAlwaysOnHotwordDetectorEnabled) {
+                    moduleProperties = getTestModuleProperties();
+                }
                 // If moduleProperties is null, the default STModule is used.
                 dspDetector.initialize(options, sharedMemory, moduleProperties);
             } catch (Exception e) {
@@ -990,6 +998,44 @@
         return mKeyphraseEnrollmentInfo;
     }
 
+
+    /**
+     * Configure {@link createAlwaysOnHotwordDetector(String, Locale,
+     * SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)}
+     * and similar overloads to utilize the test SoundTrigger module instead of the
+     * actual DSP module.
+     * @param isEnabled - {@code true} if subsequently created {@link AlwaysOnHotwordDetector}
+     * objects should attach to a test module. {@code false} if subsequently created
+     * {@link AlwaysOnHotwordDetector} should attach to the actual DSP module.
+     * @hide
+     */
+    @TestApi
+    public final void setTestModuleForAlwaysOnHotwordDetectorEnabled(boolean isEnabled) {
+        synchronized (mLock) {
+            mTestModuleForAlwaysOnHotwordDetectorEnabled = isEnabled;
+        }
+    }
+
+    /**
+     * Get the {@link SoundTrigger.ModuleProperties} representing the fake
+     * STHAL to attach to via {@link createAlwaysOnHotwordDetector(String, Locale,
+     * SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)} and
+     * similar overloads for test purposes.
+     * @return ModuleProperties to use for test purposes.
+     */
+    private final @NonNull SoundTrigger.ModuleProperties getTestModuleProperties() {
+        var moduleProps = listModuleProperties()
+                .stream()
+                .filter((SoundTrigger.ModuleProperties prop)
+                        -> prop.getSupportedModelArch().equals(SoundTrigger.FAKE_HAL_ARCH))
+                .findFirst()
+                .orElse(null);
+        if (moduleProps == null) {
+            throw new IllegalStateException("Fake ST HAL should always be available");
+        }
+        return moduleProps;
+    }
+
     /**
      * Checks if a given keyphrase and locale are supported to create an
      * {@link AlwaysOnHotwordDetector}.
diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
index 4c51be0..f1ae22e 100644
--- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
@@ -32,6 +32,8 @@
     oneway void setDisplayPadding(in Rect padding);
     @UnsupportedAppUsage
     oneway void setVisibility(boolean visible);
+    oneway void onScreenTurningOn();
+    oneway void onScreenTurnedOn();
     oneway void setInAmbientMode(boolean inAmbientDisplay, long animationDuration);
     @UnsupportedAppUsage
     oneway void dispatchPointer(in MotionEvent event);
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 0b947fc..74ab709 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -112,6 +112,7 @@
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Supplier;
 
 /**
@@ -168,6 +169,7 @@
     private static final int MSG_ZOOM = 10100;
     private static final int MSG_RESIZE_PREVIEW = 10110;
     private static final int MSG_REPORT_SHOWN = 10150;
+    private static final int MSG_UPDATE_SCREEN_TURNING_ON = 10170;
     private static final int MSG_UPDATE_DIMMING = 10200;
     private static final int MSG_WALLPAPER_FLAGS_CHANGED = 10210;
 
@@ -213,6 +215,16 @@
 
         boolean mInitializing = true;
         boolean mVisible;
+        /**
+         * Whether the screen is turning on.
+         * After the display is powered on, brightness is initially off. It is turned on only after
+         * all windows have been drawn, and sysui notifies that it's ready (See
+         * {@link com.android.internal.policy.IKeyguardDrawnCallback}).
+         * As some wallpapers use visibility as a signal to start animations, this makes sure
+         * {@link Engine#onVisibilityChanged} is invoked only when the display is both on and
+         * visible (with brightness on).
+         */
+        private boolean mIsScreenTurningOn;
         boolean mReportedVisible;
         boolean mDestroyed;
         // Set to true after receiving WallpaperManager#COMMAND_FREEZE. It's reset back to false
@@ -420,6 +432,7 @@
                 Message msg = mCaller.obtainMessageIO(MSG_WINDOW_RESIZED,
                         reportDraw ? 1 : 0,
                         mergedConfiguration);
+                mIWallpaperEngine.mPendingResizeCount.incrementAndGet();
                 mCaller.sendMessage(msg);
             }
 
@@ -499,6 +512,7 @@
         public Engine(Supplier<Long> clockFunction, Handler handler) {
             mClockFunction = clockFunction;
             mHandler = handler;
+            mMergedConfiguration.setOverrideConfiguration(getResources().getConfiguration());
         }
 
         /**
@@ -1018,6 +1032,7 @@
                     out.print(" mDestroyed="); out.println(mDestroyed);
             out.print(prefix); out.print("mVisible="); out.print(mVisible);
                     out.print(" mReportedVisible="); out.println(mReportedVisible);
+                    out.print(" mIsScreenTurningOn="); out.println(mIsScreenTurningOn);
             out.print(prefix); out.print("mDisplay="); out.println(mDisplay);
             out.print(prefix); out.print("mCreated="); out.print(mCreated);
                     out.print(" mSurfaceCreated="); out.print(mSurfaceCreated);
@@ -1039,6 +1054,10 @@
             out.print(prefix); out.print("mZoom="); out.println(mZoom);
             out.print(prefix); out.print("mPreviewSurfacePosition=");
                     out.println(mPreviewSurfacePosition);
+            final int pendingCount = mIWallpaperEngine.mPendingResizeCount.get();
+            if (pendingCount != 0) {
+                out.print(prefix); out.print("mPendingResizeCount="); out.println(pendingCount);
+            }
             synchronized (mLock) {
                 out.print(prefix); out.print("mPendingXOffset="); out.print(mPendingXOffset);
                         out.print(" mPendingXOffset="); out.println(mPendingXOffset);
@@ -1101,10 +1120,6 @@
             }
         }
 
-        private void updateConfiguration(MergedConfiguration mergedConfiguration) {
-            mMergedConfiguration.setTo(mergedConfiguration);
-        }
-
         void updateSurface(boolean forceRelayout, boolean forceReport, boolean redrawNeeded) {
             if (mDestroyed) {
                 Log.w(TAG, "Ignoring updateSurface due to destroyed");
@@ -1153,7 +1168,7 @@
                             | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 
                     final Configuration config = mMergedConfiguration.getMergedConfiguration();
-                    final Rect maxBounds = config.windowConfiguration.getMaxBounds();
+                    final Rect maxBounds = new Rect(config.windowConfiguration.getMaxBounds());
                     if (myWidth == ViewGroup.LayoutParams.MATCH_PARENT
                             && myHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
                         mLayout.width = myWidth;
@@ -1209,6 +1224,17 @@
                     final int relayoutResult = mSession.relayout(mWindow, mLayout, mWidth, mHeight,
                             View.VISIBLE, 0, 0, 0, mWinFrames, mMergedConfiguration,
                             mSurfaceControl, mInsetsState, mTempControls, mSyncSeqIdBundle);
+                    final Rect outMaxBounds = mMergedConfiguration.getMergedConfiguration()
+                            .windowConfiguration.getMaxBounds();
+                    if (!outMaxBounds.equals(maxBounds)) {
+                        Log.i(TAG, "Retry updateSurface because bounds changed from relayout: "
+                                + maxBounds + " -> " + outMaxBounds);
+                        mSurfaceHolder.mSurfaceLock.unlock();
+                        mDrawingAllowed = false;
+                        mCaller.sendMessage(mCaller.obtainMessageI(MSG_WINDOW_RESIZED,
+                                redrawNeeded ? 1 : 0));
+                        return;
+                    }
 
                     final int transformHint = SurfaceControl.rotationToBufferTransform(
                             (mDisplay.getInstallOrientation() + mDisplay.getRotation()) % 4);
@@ -1549,6 +1575,13 @@
             }
         }
 
+        void onScreenTurningOnChanged(boolean isScreenTurningOn) {
+            if (!mDestroyed) {
+                mIsScreenTurningOn = isScreenTurningOn;
+                reportVisibility(false);
+            }
+        }
+
         void doVisibilityChanged(boolean visible) {
             if (!mDestroyed) {
                 mVisible = visible;
@@ -1565,9 +1598,10 @@
                 return;
             }
             if (!mDestroyed) {
-                mDisplayState = mDisplay == null ? Display.STATE_UNKNOWN :
-                        mDisplay.getCommittedState();
-                boolean visible = mVisible && mDisplayState != Display.STATE_OFF;
+                mDisplayState =
+                        mDisplay == null ? Display.STATE_UNKNOWN : mDisplay.getCommittedState();
+                boolean displayVisible = Display.isOnState(mDisplayState) && !mIsScreenTurningOn;
+                boolean visible = mVisible && displayVisible;
                 if (DEBUG) {
                     Log.v(
                             TAG,
@@ -2304,6 +2338,8 @@
         final IBinder mWindowToken;
         final int mWindowType;
         final boolean mIsPreview;
+        final AtomicInteger mPendingResizeCount = new AtomicInteger();
+        boolean mReportDraw;
         boolean mShownReported;
         int mReqWidth;
         int mReqHeight;
@@ -2486,6 +2522,20 @@
             }
         }
 
+        public void updateScreenTurningOn(boolean isScreenTurningOn) {
+            Message msg = mCaller.obtainMessageBO(MSG_UPDATE_SCREEN_TURNING_ON, isScreenTurningOn,
+                    null);
+            mCaller.sendMessage(msg);
+        }
+
+        public void onScreenTurningOn() throws RemoteException {
+            updateScreenTurningOn(true);
+        }
+
+        public void onScreenTurnedOn() throws RemoteException {
+            updateScreenTurningOn(false);
+        }
+
         @Override
         public void executeMessage(Message message) {
             switch (message.what) {
@@ -2530,6 +2580,13 @@
                             + ": " + message.arg1);
                     mEngine.doVisibilityChanged(message.arg1 != 0);
                     break;
+                case MSG_UPDATE_SCREEN_TURNING_ON:
+                    if (DEBUG) {
+                        Log.v(TAG,
+                                message.arg1 != 0 ? "Screen turning on" : "Screen turned on");
+                    }
+                    mEngine.onScreenTurningOnChanged(/* isScreenTurningOn= */ message.arg1 != 0);
+                    break;
                 case MSG_WALLPAPER_OFFSETS: {
                     mEngine.doOffsetsChanged(true);
                 } break;
@@ -2538,11 +2595,7 @@
                     mEngine.doCommand(cmd);
                 } break;
                 case MSG_WINDOW_RESIZED: {
-                    final boolean reportDraw = message.arg1 != 0;
-                    mEngine.updateConfiguration(((MergedConfiguration) message.obj));
-                    mEngine.updateSurface(true, false, reportDraw);
-                    mEngine.doOffsetsChanged(true);
-                    mEngine.scaleAndCropScreenshot();
+                    handleResized((MergedConfiguration) message.obj, message.arg1 != 0);
                 } break;
                 case MSG_WINDOW_MOVED: {
                     // Do nothing. What does it mean for a Wallpaper to move?
@@ -2590,6 +2643,40 @@
                     Log.w(TAG, "Unknown message type " + message.what);
             }
         }
+
+        /**
+         * In general this performs relayout for IWindow#resized. If there are several pending
+         * (in the message queue) MSG_WINDOW_RESIZED from server side, only the last one will be
+         * handled (ignore intermediate states). Note that this procedure cannot be skipped if the
+         * configuration is not changed because this is also used to dispatch insets changes.
+         */
+        private void handleResized(MergedConfiguration config, boolean reportDraw) {
+            // The config can be null when retrying for a changed config from relayout, otherwise
+            // it is from IWindow#resized which always sends non-null config.
+            final int pendingCount = config != null ? mPendingResizeCount.decrementAndGet() : -1;
+            if (reportDraw) {
+                mReportDraw = true;
+            }
+            if (pendingCount > 0) {
+                if (DEBUG) {
+                    Log.d(TAG, "Skip outdated resize, bounds="
+                            + config.getMergedConfiguration().windowConfiguration.getMaxBounds()
+                            + " pendingCount=" + pendingCount);
+                }
+                return;
+            }
+            if (config != null) {
+                if (DEBUG) {
+                    Log.d(TAG, "Update config from resized, bounds="
+                            + config.getMergedConfiguration().windowConfiguration.getMaxBounds());
+                }
+                mEngine.mMergedConfiguration.setTo(config);
+            }
+            mEngine.updateSurface(true /* forceRelayout */, false /* forceReport */, mReportDraw);
+            mReportDraw = false;
+            mEngine.doOffsetsChanged(true);
+            mEngine.scaleAndCropScreenshot();
+        }
     }
 
     /**
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index c329508..d87198a0 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -128,6 +128,12 @@
      */
     public static final String SETTINGS_ENABLE_SPA_PHASE2 = "settings_enable_spa_phase2";
 
+    /**
+     * Enable the SPA metrics writing.
+     * @hide
+     */
+    public static final String SETTINGS_ENABLE_SPA_METRICS = "settings_enable_spa_metrics";
+
     /** Flag to enable/disable adb log metrics
      *  @hide
      */
@@ -226,6 +232,7 @@
         DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE, "false");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "true");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA_PHASE2, "false");
+        DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA_METRICS, "false");
         DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false");
         DEFAULT_FLAGS.put(SETTINGS_SHOW_STYLUS_PREFERENCES, "true");
         DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false");
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index 9a93e1b..d06b0ce 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -354,6 +354,15 @@
     }
 
     /** @hide Just for debugging; not internationalized. */
+    public static void formatDuration(long time, long now, StringBuilder sb) {
+        if (time == 0) {
+            sb.append("--");
+            return;
+        }
+        formatDuration(time-now, sb, 0);
+    }
+
+    /** @hide Just for debugging; not internationalized. */
     public static void formatDuration(long time, long now, PrintWriter pw) {
         if (time == 0) {
             pw.print("--");
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 8c4e90c..edc5993 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -195,7 +195,7 @@
 
     private boolean mDebugPrintNextFrameTimeDelta;
     private int mFPSDivisor = 1;
-    private DisplayEventReceiver.VsyncEventData mLastVsyncEventData =
+    private final DisplayEventReceiver.VsyncEventData mLastVsyncEventData =
             new DisplayEventReceiver.VsyncEventData();
     private final FrameData mFrameData = new FrameData();
 
@@ -785,12 +785,13 @@
             DisplayEventReceiver.VsyncEventData vsyncEventData) {
         final long startNanos;
         final long frameIntervalNanos = vsyncEventData.frameInterval;
+        boolean resynced = false;
         try {
+            FrameTimeline timeline = mFrameData.update(frameTimeNanos, vsyncEventData);
             if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-                Trace.traceBegin(Trace.TRACE_TAG_VIEW,
-                        "Choreographer#doFrame " + vsyncEventData.preferredFrameTimeline().vsyncId);
+                Trace.traceBegin(
+                        Trace.TRACE_TAG_VIEW, "Choreographer#doFrame " + timeline.mVsyncId);
             }
-            mFrameData.update(frameTimeNanos, vsyncEventData);
             synchronized (mLock) {
                 if (!mFrameScheduled) {
                     traceMessage("Frame not scheduled");
@@ -828,7 +829,9 @@
                                     + " ms in the past.");
                         }
                     }
-                    mFrameData.update(frameTimeNanos, mDisplayEventReceiver, jitterNanos);
+                    timeline = mFrameData.update(
+                            frameTimeNanos, mDisplayEventReceiver, jitterNanos);
+                    resynced = true;
                 }
 
                 if (frameTimeNanos < mLastFrameTimeNanos) {
@@ -857,7 +860,13 @@
                 mFrameScheduled = false;
                 mLastFrameTimeNanos = frameTimeNanos;
                 mLastFrameIntervalNanos = frameIntervalNanos;
-                mLastVsyncEventData = vsyncEventData;
+                mLastVsyncEventData.copyFrom(vsyncEventData);
+            }
+
+            if (resynced && Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                String message = String.format("Choreographer#doFrame - resynced to %d in %.1fms",
+                        timeline.mVsyncId, (timeline.mDeadlineNanos - startNanos) * 0.000001f);
+                Trace.traceBegin(Trace.TRACE_TAG_VIEW, message);
             }
 
             AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
@@ -875,6 +884,9 @@
             doCallbacks(Choreographer.CALLBACK_COMMIT, frameIntervalNanos);
         } finally {
             AnimationUtils.unlockAnimationClock();
+            if (resynced) {
+                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            }
             Trace.traceEnd(Trace.TRACE_TAG_VIEW);
         }
 
@@ -1149,7 +1161,8 @@
          * Update the frame data with a {@code DisplayEventReceiver.VsyncEventData} received from
          * native.
          */
-        void update(long frameTimeNanos, DisplayEventReceiver.VsyncEventData vsyncEventData) {
+        FrameTimeline update(
+                long frameTimeNanos, DisplayEventReceiver.VsyncEventData vsyncEventData) {
             if (vsyncEventData.frameTimelines.length != mFrameTimelines.length) {
                 throw new IllegalStateException(
                         "Length of native frame timelines received does not match Java. Did "
@@ -1164,6 +1177,7 @@
                 mFrameTimelines[i].update(frameTimeline.vsyncId,
                         frameTimeline.expectedPresentationTime, frameTimeline.deadline);
             }
+            return mFrameTimelines[mPreferredFrameTimelineIndex];
         }
 
         /**
@@ -1171,7 +1185,7 @@
          *
          * @param jitterNanos currentTime - frameTime
          */
-        void update(
+        FrameTimeline update(
                 long frameTimeNanos, DisplayEventReceiver displayEventReceiver, long jitterNanos) {
             int newPreferredIndex = 0;
             final long minimumDeadline =
@@ -1192,6 +1206,7 @@
             } else {
                 update(frameTimeNanos, newPreferredIndex);
             }
+            return mFrameTimelines[mPreferredFrameTimelineIndex];
         }
 
         void update(long frameTimeNanos, int newPreferredFrameTimelineIndex) {
@@ -1247,7 +1262,7 @@
         private boolean mHavePendingVsync;
         private long mTimestampNanos;
         private int mFrame;
-        private VsyncEventData mLastVsyncEventData = new VsyncEventData();
+        private final VsyncEventData mLastVsyncEventData = new VsyncEventData();
 
         FrameDisplayEventReceiver(Looper looper, int vsyncSource, long layerHandle) {
             super(looper, vsyncSource, /* eventRegistration */ 0, layerHandle);
@@ -1287,7 +1302,7 @@
 
                 mTimestampNanos = timestampNanos;
                 mFrame = frame;
-                mLastVsyncEventData = vsyncEventData;
+                mLastVsyncEventData.copyFrom(vsyncEventData);
                 Message msg = Message.obtain(mHandler, this);
                 msg.setAsynchronous(true);
                 mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index 0307489..54db34e 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -81,7 +81,10 @@
     // GC'd while the native peer of the receiver is using them.
     private MessageQueue mMessageQueue;
 
+    private final VsyncEventData mVsyncEventData = new VsyncEventData();
+
     private static native long nativeInit(WeakReference<DisplayEventReceiver> receiver,
+            WeakReference<VsyncEventData> vsyncEventData,
             MessageQueue messageQueue, int vsyncSource, int eventRegistration, long layerHandle);
     private static native long nativeGetDisplayEventReceiverFinalizer();
     @FastNative
@@ -124,7 +127,9 @@
         }
 
         mMessageQueue = looper.getQueue();
-        mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue,
+        mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this),
+                new WeakReference<VsyncEventData>(mVsyncEventData),
+                mMessageQueue,
                 vsyncSource, eventRegistration, layerHandle);
         mFreeNativeResources = sNativeAllocationRegistry.registerNativeAllocation(this,
                 mReceiverPtr);
@@ -147,9 +152,6 @@
      * @hide
      */
     public static final class VsyncEventData {
-        static final FrameTimeline[] INVALID_FRAME_TIMELINES =
-                {new FrameTimeline(FrameInfo.INVALID_VSYNC_ID, Long.MAX_VALUE, Long.MAX_VALUE)};
-
         // The amount of frame timeline choices.
         // Must be in sync with VsyncEventData::kFrameTimelinesLength in
         // frameworks/native/libs/gui/include/gui/VsyncEventData.h. If they do not match, a runtime
@@ -157,22 +159,32 @@
         static final int FRAME_TIMELINES_LENGTH = 7;
 
         public static class FrameTimeline {
+            FrameTimeline() {}
+
+            // Called from native code.
+            @SuppressWarnings("unused")
             FrameTimeline(long vsyncId, long expectedPresentationTime, long deadline) {
                 this.vsyncId = vsyncId;
                 this.expectedPresentationTime = expectedPresentationTime;
                 this.deadline = deadline;
             }
 
+            void copyFrom(FrameTimeline other) {
+                vsyncId = other.vsyncId;
+                expectedPresentationTime = other.expectedPresentationTime;
+                deadline = other.deadline;
+            }
+
             // The frame timeline vsync id, used to correlate a frame
             // produced by HWUI with the timeline data stored in Surface Flinger.
-            public final long vsyncId;
+            public long vsyncId = FrameInfo.INVALID_VSYNC_ID;
 
             // The frame timestamp for when the frame is expected to be presented.
-            public final long expectedPresentationTime;
+            public long expectedPresentationTime = Long.MAX_VALUE;
 
             // The frame deadline timestamp in {@link System#nanoTime()} timebase that it is
             // allotted for the frame to be completed.
-            public final long deadline;
+            public long deadline = Long.MAX_VALUE;
         }
 
         /**
@@ -180,11 +192,18 @@
          * {@link FrameInfo#VSYNC} to the current vsync in case Choreographer callback was heavily
          * delayed by the app.
          */
-        public final long frameInterval;
+        public long frameInterval = -1;
 
         public final FrameTimeline[] frameTimelines;
 
-        public final int preferredFrameTimelineIndex;
+        public int preferredFrameTimelineIndex = 0;
+
+        VsyncEventData() {
+            frameTimelines = new FrameTimeline[FRAME_TIMELINES_LENGTH];
+            for (int i = 0; i < frameTimelines.length; i++) {
+                frameTimelines[i] = new FrameTimeline();
+            }
+        }
 
         // Called from native code.
         @SuppressWarnings("unused")
@@ -195,10 +214,12 @@
             this.frameInterval = frameInterval;
         }
 
-        VsyncEventData() {
-            this.frameInterval = -1;
-            this.frameTimelines = INVALID_FRAME_TIMELINES;
-            this.preferredFrameTimelineIndex = 0;
+        void copyFrom(VsyncEventData other) {
+            preferredFrameTimelineIndex = other.preferredFrameTimelineIndex;
+            frameInterval = other.frameInterval;
+            for (int i = 0; i < frameTimelines.length; i++) {
+                frameTimelines[i].copyFrom(other.frameTimelines[i]);
+            }
         }
 
         public FrameTimeline preferredFrameTimeline() {
@@ -304,9 +325,8 @@
 
     // Called from native code.
     @SuppressWarnings("unused")
-    private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame,
-            VsyncEventData vsyncEventData) {
-        onVsync(timestampNanos, physicalDisplayId, frame, vsyncEventData);
+    private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) {
+        onVsync(timestampNanos, physicalDisplayId, frame, mVsyncEventData);
     }
 
     // Called from native code.
diff --git a/core/java/android/view/InputEvent.java b/core/java/android/view/InputEvent.java
index 0b4adae..a8e68b71 100644
--- a/core/java/android/view/InputEvent.java
+++ b/core/java/android/view/InputEvent.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -100,6 +101,7 @@
      * @return The display id associated with the event.
      * @hide
      */
+    @TestApi
     public abstract int getDisplayId();
 
     /**
@@ -107,6 +109,7 @@
      * @param displayId
      * @hide
      */
+    @TestApi
     public abstract void setDisplayId(int displayId);
     /**
      * Copies the event.
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index b6d9400..858da55 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -2100,6 +2100,7 @@
     }
 
     /** @hide */
+    @TestApi
     @Override
     public final int getDisplayId() {
         return mDisplayId;
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index cd89a56..bd6224b 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -120,6 +120,8 @@
 
     private ISurfaceControlViewHost mRemoteInterface = new ISurfaceControlViewHostImpl();
 
+    private ViewRootImpl.ConfigChangedCallback mConfigChangedCallback;
+
     /**
      * Package encapsulating a Surface hierarchy which contains interactive view
      * elements. It's expected to get this object from
@@ -303,10 +305,11 @@
     /** @hide */
     public SurfaceControlViewHost(@NonNull Context c, @NonNull Display d,
             @NonNull WindowlessWindowManager wwm, @NonNull String callsite) {
+        mSurfaceControl = wwm.mRootSurface;
         mWm = wwm;
         mViewRoot = new ViewRootImpl(c, d, mWm, new WindowlessWindowLayout());
         mCloseGuard.openWithCallSite("release", callsite);
-        addConfigCallback(c, d);
+        setConfigCallback(c, d);
 
         WindowManagerGlobal.getInstance().addWindowlessRoot(mViewRoot);
 
@@ -356,21 +359,23 @@
 
         mViewRoot = new ViewRootImpl(context, display, mWm, new WindowlessWindowLayout());
         mCloseGuard.openWithCallSite("release", callsite);
-        addConfigCallback(context, display);
+        setConfigCallback(context, display);
 
         WindowManagerGlobal.getInstance().addWindowlessRoot(mViewRoot);
 
         mAccessibilityEmbeddedConnection = mViewRoot.getAccessibilityEmbeddedConnection();
     }
 
-    private void addConfigCallback(Context c, Display d) {
+    private void setConfigCallback(Context c, Display d) {
         final IBinder token = c.getWindowContextToken();
-        mViewRoot.addConfigCallback((conf) -> {
+        mConfigChangedCallback = conf -> {
             if (token instanceof WindowTokenClient) {
                 final WindowTokenClient w = (WindowTokenClient)  token;
                 w.onConfigurationChanged(conf, d.getDisplayId(), true);
             }
-        });
+        };
+
+        ViewRootImpl.addConfigCallback(mConfigChangedCallback);
     }
 
     /**
@@ -385,8 +390,7 @@
             mCloseGuard.warnIfOpen();
         }
         // We aren't on the UI thread here so we need to pass false to doDie
-        mViewRoot.die(false /* immediate */);
-        WindowManagerGlobal.getInstance().removeWindowlessRoot(mViewRoot);
+        doRelease(false /* immediate */);
     }
 
     /**
@@ -399,8 +403,7 @@
     public @Nullable SurfacePackage getSurfacePackage() {
         if (mSurfaceControl != null && mAccessibilityEmbeddedConnection != null) {
             return new SurfacePackage(new SurfaceControl(mSurfaceControl, "getSurfacePackage"),
-                mAccessibilityEmbeddedConnection,
-                mWm.getFocusGrantToken(), mRemoteInterface);
+                mAccessibilityEmbeddedConnection, getFocusGrantToken(), mRemoteInterface);
         } else {
             return null;
         }
@@ -496,7 +499,16 @@
      */
     public void release() {
         // ViewRoot will release mSurfaceControl for us.
-        mViewRoot.die(true /* immediate */);
+        doRelease(true /* immediate */);
+    }
+
+    private void doRelease(boolean immediate) {
+        if (mConfigChangedCallback != null) {
+            ViewRootImpl.removeConfigCallback(mConfigChangedCallback);
+            mConfigChangedCallback = null;
+        }
+
+        mViewRoot.die(immediate);
         WindowManagerGlobal.getInstance().removeWindowlessRoot(mViewRoot);
         mReleased = true;
         mCloseGuard.close();
@@ -506,7 +518,7 @@
      * @hide
      */
     public IBinder getFocusGrantToken() {
-        return mWm.getFocusGrantToken();
+        return mWm.getFocusGrantToken(getWindowToken().asBinder());
     }
 
     private void addWindowToken(WindowManager.LayoutParams attrs) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2f5cd54..055b5cb 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -6946,6 +6946,7 @@
                 return;
             }
             final boolean needsStylusPointerIcon = event.isStylusPointer()
+                    && event.isHoverEvent()
                     && mInputManager.isStylusPointerIconEnabled();
             if (needsStylusPointerIcon || event.isFromSource(InputDevice.SOURCE_MOUSE)) {
                 if (event.getActionMasked() == MotionEvent.ACTION_HOVER_ENTER
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 9868144..96bfb2d 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -106,8 +106,22 @@
         mConfiguration.setTo(configuration);
     }
 
-    IBinder getFocusGrantToken() {
-        return mFocusGrantToken;
+    IBinder getFocusGrantToken(IBinder window) {
+        synchronized (this) {
+            // This can only happen if someone requested the focusGrantToken before setView was
+            // called for the SCVH. In that case, use the root focusGrantToken since this will be
+            // the same token sent to WMS for the root window once setView is called.
+            if (mStateForWindow.isEmpty()) {
+                return mFocusGrantToken;
+            }
+            State state = mStateForWindow.get(window);
+            if (state != null) {
+                return state.mFocusGrantToken;
+            }
+        }
+
+        Log.w(TAG, "Failed to get focusGrantToken. Returning null token");
+        return null;
     }
 
     /**
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 1fac142..390503b 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -62,7 +62,7 @@
             in IAccessibilityInteractionConnection connection);
 
     void registerUiTestAutomationService(IBinder owner, IAccessibilityServiceClient client,
-        in AccessibilityServiceInfo info, int flags);
+        in AccessibilityServiceInfo info, int userId, int flags);
 
     void unregisterUiTestAutomationService(IAccessibilityServiceClient client);
 
diff --git a/core/java/android/view/translation/Translator.java b/core/java/android/view/translation/Translator.java
index 70db6e5..50249da 100644
--- a/core/java/android/view/translation/Translator.java
+++ b/core/java/android/view/translation/Translator.java
@@ -18,7 +18,6 @@
 
 import static android.view.translation.TranslationManager.STATUS_SYNC_CALL_FAIL;
 import static android.view.translation.TranslationManager.SYNC_CALLS_TIMEOUT_MS;
-import static android.view.translation.UiTranslationController.DEBUG;
 
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
@@ -402,7 +401,7 @@
 
         @Override
         public void onTranslationResponse(TranslationResponse response) throws RemoteException {
-            if (DEBUG) {
+            if (Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG)) {
                 Log.i(TAG, "onTranslationResponse called.");
             }
             final Runnable runnable =
diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java
index 514df59..140e3f1 100644
--- a/core/java/android/view/translation/UiTranslationController.java
+++ b/core/java/android/view/translation/UiTranslationController.java
@@ -122,8 +122,9 @@
             Log.i(TAG, "Cannot update " + stateToString(state) + " for destroyed " + mActivity);
             return;
         }
+        boolean isLoggable = Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG);
         Log.i(TAG, "updateUiTranslationState state: " + stateToString(state)
-                + (DEBUG ? (", views: " + views + ", spec: " + uiTranslationSpec) : ""));
+                + (isLoggable ? (", views: " + views + ", spec: " + uiTranslationSpec) : ""));
         synchronized (mLock) {
             mCurrentState = state;
             if (views != null) {
@@ -237,7 +238,7 @@
             }
             pw.print(outerPrefix); pw.print("padded views: "); pw.println(mViewsToPadContent);
         }
-        if (DEBUG) {
+        if (Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG)) {
             dumpViewByTraversal(outerPrefix, pw);
         }
     }
@@ -345,6 +346,7 @@
      */
     private void onVirtualViewTranslationCompleted(
             SparseArray<LongSparseArray<ViewTranslationResponse>> translatedResult) {
+        boolean isLoggable = Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG);
         if (mActivity.isDestroyed()) {
             Log.v(TAG, "onTranslationCompleted:" + mActivity + "is destroyed.");
             return;
@@ -369,7 +371,7 @@
                 }
                 final LongSparseArray<ViewTranslationResponse> virtualChildResponse =
                         translatedResult.valueAt(i);
-                if (DEBUG) {
+                if (isLoggable) {
                     Log.v(TAG, "onVirtualViewTranslationCompleted: received response for "
                             + "AutofillId " + autofillId);
                 }
@@ -379,7 +381,7 @@
                 }
                 mActivity.runOnUiThread(() -> {
                     if (view.getViewTranslationCallback() == null) {
-                        if (DEBUG) {
+                        if (isLoggable) {
                             Log.d(TAG, view + " doesn't support showing translation because of "
                                     + "null ViewTranslationCallback.");
                         }
@@ -397,12 +399,13 @@
      * The method is used to handle the translation result for non-vertual views.
      */
     private void onTranslationCompleted(SparseArray<ViewTranslationResponse> translatedResult) {
+        boolean isLoggable = Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG);
         if (mActivity.isDestroyed()) {
             Log.v(TAG, "onTranslationCompleted:" + mActivity + "is destroyed.");
             return;
         }
         final int resultCount = translatedResult.size();
-        if (DEBUG) {
+        if (isLoggable) {
             Log.v(TAG, "onTranslationCompleted: receive " + resultCount + " responses.");
         }
         synchronized (mLock) {
@@ -413,7 +416,7 @@
             }
             for (int i = 0; i < resultCount; i++) {
                 final ViewTranslationResponse response = translatedResult.valueAt(i);
-                if (DEBUG) {
+                if (isLoggable) {
                     Log.v(TAG, "onTranslationCompleted: "
                             + sanitizedViewTranslationResponse(response));
                 }
@@ -443,7 +446,7 @@
                                     (TextViewTranslationCallback) callback;
                             if (textViewCallback.isShowingTranslation()
                                     || textViewCallback.isAnimationRunning()) {
-                                if (DEBUG) {
+                                if (isLoggable) {
                                     Log.d(TAG, "Duplicate ViewTranslationResponse for " + autofillId
                                             + ". Ignoring.");
                                 }
@@ -458,7 +461,7 @@
                             callback = new TextViewTranslationCallback();
                             view.setViewTranslationCallback(callback);
                         } else {
-                            if (DEBUG) {
+                            if (isLoggable) {
                                 Log.d(TAG, view + " doesn't support showing translation because of "
                                         + "null ViewTranslationCallback.");
                             }
@@ -506,7 +509,7 @@
         final TranslationRequest request = new TranslationRequest.Builder()
                 .setViewTranslationRequests(requests)
                 .build();
-        if (DEBUG) {
+        if (Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG)) {
             StringBuilder msg = new StringBuilder("sendTranslationRequest:{requests=[");
             for (ViewTranslationRequest viewRequest: requests) {
                 msg.append("{request=")
@@ -636,6 +639,7 @@
 
     private void runForEachView(BiConsumer<View, ViewTranslationCallback> action) {
         synchronized (mLock) {
+            boolean isLoggable = Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG);
             final ArrayMap<AutofillId, WeakReference<View>> views = new ArrayMap<>(mViews);
             if (views.size() == 0) {
                 Log.w(TAG, "No views can be excuted for runForEachView.");
@@ -644,12 +648,12 @@
                 final int viewCounts = views.size();
                 for (int i = 0; i < viewCounts; i++) {
                     final View view = views.valueAt(i).get();
-                    if (DEBUG) {
+                    if (isLoggable) {
                         Log.d(TAG, "runForEachView for autofillId = " + (view != null
                                 ? view.getAutofillId() : " null"));
                     }
                     if (view == null || view.getViewTranslationCallback() == null) {
-                        if (DEBUG) {
+                        if (isLoggable) {
                             Log.d(TAG, "View was gone or ViewTranslationCallback for autofillId "
                                     + "= " + views.keyAt(i));
                         }
diff --git a/core/java/android/window/BackMotionEvent.java b/core/java/android/window/BackMotionEvent.java
index 8012a1c..c475723 100644
--- a/core/java/android/window/BackMotionEvent.java
+++ b/core/java/android/window/BackMotionEvent.java
@@ -34,6 +34,8 @@
     private final float mTouchX;
     private final float mTouchY;
     private final float mProgress;
+    private final float mVelocityX;
+    private final float mVelocityY;
 
     @BackEvent.SwipeEdge
     private final int mSwipeEdge;
@@ -43,19 +45,32 @@
     /**
      * Creates a new {@link BackMotionEvent} instance.
      *
+     * <p>Note: Velocity is only computed for last event, for performance reasons.</p>
+     *
      * @param touchX Absolute X location of the touch point of this event.
      * @param touchY Absolute Y location of the touch point of this event.
      * @param progress Value between 0 and 1 on how far along the back gesture is.
+     * @param velocityX X velocity computed from the touch point of this event.
+     *                  Value in pixels/second. {@link Float#NaN} if was not computed.
+     * @param velocityY Y velocity computed from the touch point of this event.
+     *                  Value in pixels/second. {@link Float#NaN} if was not computed.
      * @param swipeEdge Indicates which edge the swipe starts from.
      * @param departingAnimationTarget The remote animation target of the departing
      *                                 application window.
      */
-    public BackMotionEvent(float touchX, float touchY, float progress,
+    public BackMotionEvent(
+            float touchX,
+            float touchY,
+            float progress,
+            float velocityX,
+            float velocityY,
             @BackEvent.SwipeEdge int swipeEdge,
             @Nullable RemoteAnimationTarget departingAnimationTarget) {
         mTouchX = touchX;
         mTouchY = touchY;
         mProgress = progress;
+        mVelocityX = velocityX;
+        mVelocityY = velocityY;
         mSwipeEdge = swipeEdge;
         mDepartingAnimationTarget = departingAnimationTarget;
     }
@@ -64,6 +79,8 @@
         mTouchX = in.readFloat();
         mTouchY = in.readFloat();
         mProgress = in.readFloat();
+        mVelocityX = in.readFloat();
+        mVelocityY = in.readFloat();
         mSwipeEdge = in.readInt();
         mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR);
     }
@@ -91,21 +108,13 @@
         dest.writeFloat(mTouchX);
         dest.writeFloat(mTouchY);
         dest.writeFloat(mProgress);
+        dest.writeFloat(mVelocityX);
+        dest.writeFloat(mVelocityY);
         dest.writeInt(mSwipeEdge);
         dest.writeTypedObject(mDepartingAnimationTarget, flags);
     }
 
     /**
-     * Returns the progress of a {@link BackEvent}.
-     *
-     * @see BackEvent#getProgress()
-     */
-    @FloatRange(from = 0, to = 1)
-    public float getProgress() {
-        return mProgress;
-    }
-
-    /**
      * Returns the absolute X location of the touch point.
      */
     public float getTouchX() {
@@ -120,6 +129,34 @@
     }
 
     /**
+     * Returns the progress of a {@link BackEvent}.
+     *
+     * @see BackEvent#getProgress()
+     */
+    @FloatRange(from = 0, to = 1)
+    public float getProgress() {
+        return mProgress;
+    }
+
+    /**
+     * Returns the X velocity computed from the touch point.
+     *
+     * @return value in pixels/second or {@link Float#NaN} if was not computed.
+     */
+    public float getVelocityX() {
+        return mVelocityX;
+    }
+
+    /**
+     * Returns the Y velocity computed from the touch point.
+     *
+     * @return value in pixels/second or {@link Float#NaN} if was not computed.
+     */
+    public float getVelocityY() {
+        return mVelocityY;
+    }
+
+    /**
      * Returns the screen edge that the swipe starts from.
      */
     @BackEvent.SwipeEdge
@@ -143,6 +180,8 @@
                 + "mTouchX=" + mTouchX
                 + ", mTouchY=" + mTouchY
                 + ", mProgress=" + mProgress
+                + ", mVelocityX=" + mVelocityX
+                + ", mVelocityY=" + mVelocityY
                 + ", mSwipeEdge" + mSwipeEdge
                 + ", mDepartingAnimationTarget" + mDepartingAnimationTarget
                 + "}";
diff --git a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
index ad0d1a4..3801188 100644
--- a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
+++ b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
@@ -20,6 +20,7 @@
 import android.service.voice.HotwordDetectedResult;
 import android.service.voice.HotwordDetectionServiceFailure;
 import android.service.voice.HotwordRejectedResult;
+import android.service.voice.SoundTriggerFailure;
 import android.service.voice.VisualQueryDetectionServiceFailure;
 
 /**
@@ -57,13 +58,6 @@
     void onRejected(in HotwordRejectedResult result);
 
     /**
-     * Called when the detection fails due to an error.
-     *
-     * @param status The error code that was seen.
-     */
-    void onError(int status);
-
-    /**
      * Called when the detection fails due to an error occurs in the
      * {@link HotwordDetectionService}.
      *
@@ -84,6 +78,15 @@
         in VisualQueryDetectionServiceFailure visualQueryDetectionServiceFailure);
 
     /**
+     * Called when the detection fails due to an error occurs in the
+     * {@link com.android.server.soundtrigger.SoundTriggerService}.
+     *
+     * @param soundTriggerFailure It provides the error code, error message and
+     *                                           suggested action.
+     */
+    void onSoundTriggerFailure(in SoundTriggerFailure soundTriggerFailure);
+
+    /**
      * Called when the detection fails due to an unknown error occurs.
      *
      * @param errorMessage It provides the error message.
diff --git a/core/java/com/android/internal/app/ISoundTriggerService.aidl b/core/java/com/android/internal/app/ISoundTriggerService.aidl
index ab7f602..ed751cb 100644
--- a/core/java/com/android/internal/app/ISoundTriggerService.aidl
+++ b/core/java/com/android/internal/app/ISoundTriggerService.aidl
@@ -16,8 +16,9 @@
 
 package com.android.internal.app;
 
-import android.media.permission.Identity;
 import android.hardware.soundtrigger.SoundTrigger;
+import android.media.permission.Identity;
+import android.media.soundtrigger_middleware.ISoundTriggerInjection;
 import com.android.internal.app.ISoundTriggerSession;
 
 /**
@@ -74,4 +75,8 @@
      */
     List<SoundTrigger.ModuleProperties> listModuleProperties(in Identity originatorIdentity);
 
+    /**
+     * Attach an HAL injection interface.
+     */
+     void attachInjection(ISoundTriggerInjection injection);
 }
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 6b40d98..24d5afc 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -96,6 +96,21 @@
      * @RequiresPermission Manifest.permission.MANAGE_VOICE_KEYPHRASES
      */
     int deleteKeyphraseSoundModel(int keyphraseId, in String bcp47Locale);
+
+    /**
+     * Override the persistent enrolled model database with an in-memory
+     * fake for testing purposes.
+     *
+     * @param enabled - {@code true} to enable the test database. {@code false} to enable
+     * the real, persistent database.
+     * @param token - IBinder used to register a death listener to clean-up the override
+     * if tests do not clean up gracefully.
+     */
+    @EnforcePermission("MANAGE_VOICE_KEYPHRASES")
+    @JavaPassthrough(annotation= "@android.annotation.RequiresPermission(" +
+            "android.Manifest.permission.MANAGE_VOICE_KEYPHRASES)")
+    void setModelDatabaseForTestEnabled(boolean enabled, IBinder token);
+
     /**
      * Indicates if there's a keyphrase sound model available for the given keyphrase ID and the
      * user ID of the caller.
@@ -106,6 +121,7 @@
      * @param bcp47Locale The BCP47 language tag  for the keyphrase's locale.
      */
     boolean isEnrolledForKeyphrase(int keyphraseId, String bcp47Locale);
+
     /**
      * Generates KeyphraseMetadata for an enrolled sound model based on keyphrase string, locale,
      * and the user ID of the caller.
diff --git a/core/java/com/android/internal/expresslog/Counter.java b/core/java/com/android/internal/expresslog/Counter.java
deleted file mode 100644
index 4a46d91..0000000
--- a/core/java/com/android/internal/expresslog/Counter.java
+++ /dev/null
@@ -1,71 +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.internal.expresslog;
-
-import android.annotation.NonNull;
-
-import com.android.internal.util.FrameworkStatsLog;
-
-/** Counter encapsulates StatsD write API calls */
-public final class Counter {
-
-    // Not instantiable.
-    private Counter() {}
-
-    /**
-     * Increments Telemetry Express Counter metric by 1
-     * @param metricId to log, no-op if metricId is not defined in the TeX catalog
-     * @hide
-     */
-    public static void logIncrement(@NonNull String metricId) {
-        logIncrement(metricId, 1);
-    }
-
-    /**
-     * Increments Telemetry Express Counter metric by 1
-     * @param metricId to log, no-op if metricId is not defined in the TeX catalog
-     * @param uid used as a dimension for the count metric
-     * @hide
-     */
-    public static void logIncrementWithUid(@NonNull String metricId, int uid) {
-        logIncrementWithUid(metricId, uid, 1);
-    }
-
-    /**
-     * Increments Telemetry Express Counter metric by arbitrary value
-     * @param metricId to log, no-op if metricId is not defined in the TeX catalog
-     * @param amount to increment counter
-     * @hide
-     */
-    public static void logIncrement(@NonNull String metricId, long amount) {
-        final long metricIdHash = Utils.hashString(metricId);
-        FrameworkStatsLog.write(FrameworkStatsLog.EXPRESS_EVENT_REPORTED, metricIdHash, amount);
-    }
-
-    /**
-     * Increments Telemetry Express Counter metric by arbitrary value
-     * @param metricId to log, no-op if metricId is not defined in the TeX catalog
-     * @param uid used as a dimension for the count metric
-     * @param amount to increment counter
-     * @hide
-     */
-    public static void logIncrementWithUid(@NonNull String metricId, int uid, long amount) {
-        final long metricIdHash = Utils.hashString(metricId);
-        FrameworkStatsLog.write(
-                FrameworkStatsLog.EXPRESS_UID_EVENT_REPORTED, metricIdHash, amount, uid);
-    }
-}
diff --git a/core/java/com/android/internal/expresslog/Histogram.java b/core/java/com/android/internal/expresslog/Histogram.java
deleted file mode 100644
index 2fe784a..0000000
--- a/core/java/com/android/internal/expresslog/Histogram.java
+++ /dev/null
@@ -1,234 +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.internal.expresslog;
-
-import android.annotation.FloatRange;
-import android.annotation.IntRange;
-import android.annotation.NonNull;
-
-import com.android.internal.util.FrameworkStatsLog;
-
-import java.util.Arrays;
-
-/** Histogram encapsulates StatsD write API calls */
-public final class Histogram {
-
-    private final long mMetricIdHash;
-    private final BinOptions mBinOptions;
-
-    /**
-     * Creates Histogram metric logging wrapper
-     *
-     * @param metricId   to log, logging will be no-op if metricId is not defined in the TeX catalog
-     * @param binOptions to calculate bin index for samples
-     * @hide
-     */
-    public Histogram(@NonNull String metricId, @NonNull BinOptions binOptions) {
-        mMetricIdHash = Utils.hashString(metricId);
-        mBinOptions = binOptions;
-    }
-
-    /**
-     * Logs increment sample count for automatically calculated bin
-     *
-     * @param sample value
-     * @hide
-     */
-    public void logSample(float sample) {
-        final int binIndex = mBinOptions.getBinForSample(sample);
-        FrameworkStatsLog.write(FrameworkStatsLog.EXPRESS_HISTOGRAM_SAMPLE_REPORTED, mMetricIdHash,
-                /*count*/ 1, binIndex);
-    }
-
-    /**
-     * Logs increment sample count for automatically calculated bin
-     *
-     * @param uid used as a dimension for the count metric
-     * @param sample value
-     * @hide
-     */
-    public void logSampleWithUid(int uid, float sample) {
-        final int binIndex = mBinOptions.getBinForSample(sample);
-        FrameworkStatsLog.write(FrameworkStatsLog.EXPRESS_UID_HISTOGRAM_SAMPLE_REPORTED,
-                mMetricIdHash, /*count*/ 1, binIndex, uid);
-    }
-
-    /** Used by Histogram to map data sample to corresponding bin */
-    public interface BinOptions {
-        /**
-         * Returns bins count to be used by a histogram
-         *
-         * @return bins count used to initialize Options, including overflow & underflow bins
-         * @hide
-         */
-        int getBinsCount();
-
-        /**
-         * Returns bin index for the input sample value
-         * index == 0 stands for underflow
-         * index == getBinsCount() - 1 stands for overflow
-         *
-         * @return zero based index
-         * @hide
-         */
-        int getBinForSample(float sample);
-    }
-
-    /** Used by Histogram to map data sample to corresponding bin for uniform bins */
-    public static final class UniformOptions implements BinOptions {
-
-        private final int mBinCount;
-        private final float mMinValue;
-        private final float mExclusiveMaxValue;
-        private final float mBinSize;
-
-        /**
-         * Creates options for uniform (linear) sized bins
-         *
-         * @param binCount          amount of histogram bins. 2 bin indexes will be calculated
-         *                          automatically to represent underflow & overflow bins
-         * @param minValue          is included in the first bin, values less than minValue
-         *                          go to underflow bin
-         * @param exclusiveMaxValue is included in the overflow bucket. For accurate
-         *                          measure up to kMax, then exclusiveMaxValue
-         *                          should be set to kMax + 1
-         * @hide
-         */
-        public UniformOptions(@IntRange(from = 1) int binCount, float minValue,
-                float exclusiveMaxValue) {
-            if (binCount < 1) {
-                throw new IllegalArgumentException("Bin count should be positive number");
-            }
-
-            if (exclusiveMaxValue <= minValue) {
-                throw new IllegalArgumentException("Bins range invalid (maxValue < minValue)");
-            }
-
-            mMinValue = minValue;
-            mExclusiveMaxValue = exclusiveMaxValue;
-            mBinSize = (mExclusiveMaxValue - minValue) / binCount;
-
-            // Implicitly add 2 for the extra underflow & overflow bins
-            mBinCount = binCount + 2;
-        }
-
-        @Override
-        public int getBinsCount() {
-            return mBinCount;
-        }
-
-        @Override
-        public int getBinForSample(float sample) {
-            if (sample < mMinValue) {
-                // goes to underflow
-                return 0;
-            } else if (sample >= mExclusiveMaxValue) {
-                // goes to overflow
-                return mBinCount - 1;
-            }
-            return (int) ((sample - mMinValue) / mBinSize + 1);
-        }
-    }
-
-    /** Used by Histogram to map data sample to corresponding bin for scaled bins */
-    public static final class ScaledRangeOptions implements BinOptions {
-        // store minimum value per bin
-        final long[] mBins;
-
-        /**
-         * Creates options for scaled range bins
-         *
-         * @param binCount      amount of histogram bins. 2 bin indexes will be calculated
-         *                      automatically to represent underflow & overflow bins
-         * @param minValue      is included in the first bin, values less than minValue
-         *                      go to underflow bin
-         * @param firstBinWidth used to represent first bin width and as a reference to calculate
-         *                      width for consecutive bins
-         * @param scaleFactor   used to calculate width for consecutive bins
-         * @hide
-         */
-        public ScaledRangeOptions(@IntRange(from = 1) int binCount, int minValue,
-                @FloatRange(from = 1.f) float firstBinWidth,
-                @FloatRange(from = 1.f) float scaleFactor) {
-            if (binCount < 1) {
-                throw new IllegalArgumentException("Bin count should be positive number");
-            }
-
-            if (firstBinWidth < 1.f) {
-                throw new IllegalArgumentException(
-                        "First bin width invalid (should be 1.f at minimum)");
-            }
-
-            if (scaleFactor < 1.f) {
-                throw new IllegalArgumentException(
-                        "Scaled factor invalid (should be 1.f at minimum)");
-            }
-
-            // precalculating bins ranges (no need to create a bin for underflow reference value)
-            mBins = initBins(binCount + 1, minValue, firstBinWidth, scaleFactor);
-        }
-
-        @Override
-        public int getBinsCount() {
-            return mBins.length + 1;
-        }
-
-        @Override
-        public int getBinForSample(float sample) {
-            if (sample < mBins[0]) {
-                // goes to underflow
-                return 0;
-            } else if (sample >= mBins[mBins.length - 1]) {
-                // goes to overflow
-                return mBins.length;
-            }
-
-            return lower_bound(mBins, (long) sample) + 1;
-        }
-
-        // To find lower bound using binary search implementation of Arrays utility class
-        private static int lower_bound(long[] array, long sample) {
-            int index = Arrays.binarySearch(array, sample);
-            // If key is not present in the array
-            if (index < 0) {
-                // Index specify the position of the key when inserted in the sorted array
-                // so the element currently present at this position will be the lower bound
-                return Math.abs(index) - 2;
-            }
-            return index;
-        }
-
-        private static long[] initBins(int count, int minValue, float firstBinWidth,
-                float scaleFactor) {
-            long[] bins = new long[count];
-            bins[0] = minValue;
-            double lastWidth = firstBinWidth;
-            for (int i = 1; i < count; i++) {
-                // current bin minValue = previous bin width * scaleFactor
-                double currentBinMinValue = bins[i - 1] + lastWidth;
-                if (currentBinMinValue > Integer.MAX_VALUE) {
-                    throw new IllegalArgumentException(
-                        "Attempted to create a bucket larger than maxint");
-                }
-
-                bins[i] = (long) currentBinMinValue;
-                lastWidth *= scaleFactor;
-            }
-            return bins;
-        }
-    }
-}
diff --git a/core/java/com/android/internal/expresslog/OWNERS b/core/java/com/android/internal/expresslog/OWNERS
deleted file mode 100644
index ee865b1..0000000
--- a/core/java/com/android/internal/expresslog/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /services/core/java/com/android/server/stats/OWNERS
diff --git a/core/java/com/android/internal/expresslog/TEST_MAPPING b/core/java/com/android/internal/expresslog/TEST_MAPPING
deleted file mode 100644
index c9b0cf8..0000000
--- a/core/java/com/android/internal/expresslog/TEST_MAPPING
+++ /dev/null
@@ -1,12 +0,0 @@
-{
-  "presubmit": [
-    {
-      "name": "ExpressLogTests",
-      "options": [
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
-      ]
-    }
-  ]
-}
\ No newline at end of file
diff --git a/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java b/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java
index e2c096c1..aa6b1c0 100644
--- a/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java
+++ b/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java
@@ -20,7 +20,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.expresslog.Counter;
+import com.android.modules.expresslog.Counter;
 
 import java.io.IOException;
 import java.util.Arrays;
diff --git a/core/java/com/android/internal/util/DumpUtils.java b/core/java/com/android/internal/util/DumpUtils.java
index f6d80a5..8fe2b9c 100644
--- a/core/java/com/android/internal/util/DumpUtils.java
+++ b/core/java/com/android/internal/util/DumpUtils.java
@@ -25,6 +25,7 @@
 import android.os.Handler;
 import android.text.TextUtils;
 import android.util.Slog;
+import android.util.SparseArray;
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
@@ -312,5 +313,85 @@
                     || cn.flattenToString().toLowerCase().contains(filterString.toLowerCase());
         };
     }
-}
 
+    /**
+     * Lambda used to dump a key (and its index) while iterating though a collection.
+     */
+    public interface KeyDumper {
+
+        /** Dumps the index and key.*/
+        void dump(int index, int key);
+    }
+
+    /**
+     * Lambda used to dump a value while iterating though a collection.
+     *
+     * @param <T> type of the value.
+     */
+    public interface ValueDumper<T> {
+
+        /** Dumps the value.*/
+        void dump(T value);
+    }
+
+    /**
+     * Dumps a sparse array.
+     */
+    public static void dumpSparseArray(PrintWriter pw, String prefix, SparseArray<?> array,
+            String name) {
+        dumpSparseArray(pw, prefix, array, name, /* keyDumper= */ null, /* valueDumper= */ null);
+    }
+
+    /**
+     * Dumps the values of a sparse array.
+     */
+    public static <T> void dumpSparseArrayValues(PrintWriter pw, String prefix,
+            SparseArray<T> array, String name) {
+        dumpSparseArray(pw, prefix, array, name, (i, k) -> {
+            pw.printf("%s%s", prefix, prefix);
+        }, /* valueDumper= */ null);
+    }
+
+    /**
+     * Dumps a sparse array, customizing each line.
+     */
+    public static <T> void dumpSparseArray(PrintWriter pw, String prefix, SparseArray<T> array,
+            String name, @Nullable KeyDumper keyDumper, @Nullable ValueDumper<T> valueDumper) {
+        int size = array.size();
+        if (size == 0) {
+            pw.print(prefix);
+            pw.print("No ");
+            pw.print(name);
+            pw.println("s");
+            return;
+        }
+        pw.print(prefix);
+        pw.print(size);
+        pw.print(' ');
+        pw.print(name);
+        pw.println("(s):");
+
+        String prefix2 = prefix + prefix;
+        for (int i = 0; i < size; i++) {
+            int key = array.keyAt(i);
+            T value = array.valueAt(i);
+            if (keyDumper != null) {
+                keyDumper.dump(i, key);
+            } else {
+                pw.print(prefix2);
+                pw.print(i);
+                pw.print(": ");
+                pw.print(key);
+                pw.print("->");
+            }
+            if (value == null) {
+                pw.print("(null)");
+            } else if (valueDumper != null) {
+                valueDumper.dump(value);
+            } else {
+                pw.print(value);
+            }
+            pw.println();
+        }
+    }
+}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index fc26766..6bec6bc 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -225,7 +225,6 @@
                 "android_security_Scrypt.cpp",
                 "com_android_internal_content_om_OverlayConfig.cpp",
                 "com_android_internal_content_om_OverlayManagerImpl.cpp",
-                "com_android_internal_expresslog_Utils.cpp",
                 "com_android_internal_net_NetworkUtilsInternal.cpp",
                 "com_android_internal_os_ClassLoaderFactory.cpp",
                 "com_android_internal_os_FuseAppLoop.cpp",
@@ -262,6 +261,7 @@
                 "libstatssocket_lazy",
                 "libskia",
                 "libtextclassifier_hash_static",
+                "libexpresslog_jni",
             ],
 
             shared_libs: [
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index b550f28..21bdf09 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -200,7 +200,7 @@
 extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env);
 extern int register_com_android_internal_content_om_OverlayConfig(JNIEnv *env);
 extern int register_com_android_internal_content_om_OverlayManagerImpl(JNIEnv* env);
-extern int register_com_android_internal_expresslog_Utils(JNIEnv* env);
+extern int register_com_android_modules_expresslog_Utils(JNIEnv* env);
 extern int register_com_android_internal_net_NetworkUtilsInternal(JNIEnv* env);
 extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env);
 extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
@@ -1586,7 +1586,7 @@
         REG_JNI(register_android_os_incremental_IncrementalManager),
         REG_JNI(register_com_android_internal_content_om_OverlayConfig),
         REG_JNI(register_com_android_internal_content_om_OverlayManagerImpl),
-        REG_JNI(register_com_android_internal_expresslog_Utils),
+        REG_JNI(register_com_android_modules_expresslog_Utils),
         REG_JNI(register_com_android_internal_net_NetworkUtilsInternal),
         REG_JNI(register_com_android_internal_os_ClassLoaderFactory),
         REG_JNI(register_com_android_internal_os_LongArrayMultiStateCounter),
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index bce53328..4e4abec 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -104,6 +104,3 @@
 
 # PM
 per-file com_android_internal_content_* = file:/PACKAGE_MANAGER_OWNERS
-
-# Stats/expresslog
-per-file *expresslog* = file:/services/core/java/com/android/server/stats/OWNERS
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 8ba4eed..e1be0cd 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -2481,36 +2481,31 @@
 
     if (jSurroundFormats == nullptr) {
         ALOGE("jSurroundFormats is NULL");
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return static_cast<jint>(AUDIO_JAVA_BAD_VALUE);
     }
     if (!env->IsInstanceOf(jSurroundFormats, gMapClass)) {
         ALOGE("getSurroundFormats not a map");
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return static_cast<jint>(AUDIO_JAVA_BAD_VALUE);
     }
 
     jint jStatus;
     unsigned int numSurroundFormats = 0;
-    audio_format_t *surroundFormats = nullptr;
-    bool *surroundFormatsEnabled = nullptr;
-    status_t status = AudioSystem::getSurroundFormats(&numSurroundFormats, surroundFormats,
-                                                      surroundFormatsEnabled);
+    status_t status = AudioSystem::getSurroundFormats(&numSurroundFormats, nullptr, nullptr);
     if (status != NO_ERROR) {
         ALOGE_IF(status != NO_ERROR, "AudioSystem::getSurroundFormats error %d", status);
-        jStatus = nativeToJavaStatus(status);
-        goto exit;
+        return nativeToJavaStatus(status);
     }
     if (numSurroundFormats == 0) {
-        jStatus = (jint)AUDIO_JAVA_SUCCESS;
-        goto exit;
+        return static_cast<jint>(AUDIO_JAVA_SUCCESS);
     }
-    surroundFormats = (audio_format_t *)calloc(numSurroundFormats, sizeof(audio_format_t));
-    surroundFormatsEnabled = (bool *)calloc(numSurroundFormats, sizeof(bool));
-    status = AudioSystem::getSurroundFormats(&numSurroundFormats, surroundFormats,
-                                             surroundFormatsEnabled);
+    auto surroundFormats = std::make_unique<audio_format_t[]>(numSurroundFormats);
+    auto surroundFormatsEnabled = std::make_unique<bool[]>(numSurroundFormats);
+    status = AudioSystem::getSurroundFormats(&numSurroundFormats, &surroundFormats[0],
+                                             &surroundFormatsEnabled[0]);
     jStatus = nativeToJavaStatus(status);
     if (status != NO_ERROR) {
         ALOGE_IF(status != NO_ERROR, "AudioSystem::getSurroundFormats error %d", status);
-        goto exit;
+        return jStatus;
     }
     for (size_t i = 0; i < numSurroundFormats; i++) {
         int audioFormat = audioFormatFromNative(surroundFormats[i]);
@@ -2526,9 +2521,6 @@
         env->DeleteLocalRef(enabled);
     }
 
-exit:
-    free(surroundFormats);
-    free(surroundFormatsEnabled);
     return jStatus;
 }
 
@@ -2538,31 +2530,28 @@
 
     if (jSurroundFormats == nullptr) {
         ALOGE("jSurroundFormats is NULL");
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return static_cast<jint>(AUDIO_JAVA_BAD_VALUE);
     }
     if (!env->IsInstanceOf(jSurroundFormats, gArrayListClass)) {
         ALOGE("jSurroundFormats not an arraylist");
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return static_cast<jint>(AUDIO_JAVA_BAD_VALUE);
     }
     jint jStatus;
     unsigned int numSurroundFormats = 0;
-    audio_format_t *surroundFormats = nullptr;
-    status_t status = AudioSystem::getReportedSurroundFormats(&numSurroundFormats, surroundFormats);
+    status_t status = AudioSystem::getReportedSurroundFormats(&numSurroundFormats, nullptr);
     if (status != NO_ERROR) {
         ALOGE_IF(status != NO_ERROR, "AudioSystem::getReportedSurroundFormats error %d", status);
-        jStatus = nativeToJavaStatus(status);
-        goto exit;
+        return nativeToJavaStatus(status);
     }
     if (numSurroundFormats == 0) {
-        jStatus = (jint)AUDIO_JAVA_SUCCESS;
-        goto exit;
+        return static_cast<jint>(AUDIO_JAVA_SUCCESS);
     }
-    surroundFormats = (audio_format_t *)calloc(numSurroundFormats, sizeof(audio_format_t));
-    status = AudioSystem::getReportedSurroundFormats(&numSurroundFormats, surroundFormats);
+    auto surroundFormats = std::make_unique<audio_format_t[]>(numSurroundFormats);
+    status = AudioSystem::getReportedSurroundFormats(&numSurroundFormats, &surroundFormats[0]);
     jStatus = nativeToJavaStatus(status);
     if (status != NO_ERROR) {
         ALOGE_IF(status != NO_ERROR, "AudioSystem::getReportedSurroundFormats error %d", status);
-        goto exit;
+        return jStatus;
     }
     for (size_t i = 0; i < numSurroundFormats; i++) {
         int audioFormat = audioFormatFromNative(surroundFormats[i]);
@@ -2576,8 +2565,6 @@
         env->DeleteLocalRef(surroundFormat);
     }
 
-exit:
-    free(surroundFormats);
     return jStatus;
 }
 
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index dd72689..410b441 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -48,12 +48,22 @@
 
     struct {
         jclass clazz;
+
         jmethodID init;
+
+        jfieldID vsyncId;
+        jfieldID expectedPresentationTime;
+        jfieldID deadline;
     } frameTimelineClassInfo;
 
     struct {
         jclass clazz;
+
         jmethodID init;
+
+        jfieldID frameInterval;
+        jfieldID preferredFrameTimelineIndex;
+        jfieldID frameTimelines;
     } vsyncEventDataClassInfo;
 
 } gDisplayEventReceiverClassInfo;
@@ -61,7 +71,7 @@
 
 class NativeDisplayEventReceiver : public DisplayEventDispatcher {
 public:
-    NativeDisplayEventReceiver(JNIEnv* env, jobject receiverWeak,
+    NativeDisplayEventReceiver(JNIEnv* env, jobject receiverWeak, jobject vsyncEventDataWeak,
                                const sp<MessageQueue>& messageQueue, jint vsyncSource,
                                jint eventRegistration, jlong layerHandle);
 
@@ -72,6 +82,7 @@
 
 private:
     jobject mReceiverWeakGlobal;
+    jobject mVsyncEventDataWeakGlobal;
     sp<MessageQueue> mMessageQueue;
 
     void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count,
@@ -85,6 +96,7 @@
 };
 
 NativeDisplayEventReceiver::NativeDisplayEventReceiver(JNIEnv* env, jobject receiverWeak,
+                                                       jobject vsyncEventDataWeak,
                                                        const sp<MessageQueue>& messageQueue,
                                                        jint vsyncSource, jint eventRegistration,
                                                        jlong layerHandle)
@@ -96,6 +108,7 @@
                                                           reinterpret_cast<IBinder*>(layerHandle))
                                                 : nullptr),
         mReceiverWeakGlobal(env->NewGlobalRef(receiverWeak)),
+        mVsyncEventDataWeakGlobal(env->NewGlobalRef(vsyncEventDataWeak)),
         mMessageQueue(messageQueue) {
     ALOGV("receiver %p ~ Initializing display event receiver.", this);
 }
@@ -154,12 +167,43 @@
     JNIEnv* env = AndroidRuntime::getJNIEnv();
 
     ScopedLocalRef<jobject> receiverObj(env, GetReferent(env, mReceiverWeakGlobal));
-    if (receiverObj.get()) {
+    ScopedLocalRef<jobject> vsyncEventDataObj(env, GetReferent(env, mVsyncEventDataWeakGlobal));
+    if (receiverObj.get() && vsyncEventDataObj.get()) {
         ALOGV("receiver %p ~ Invoking vsync handler.", this);
 
-        jobject javaVsyncEventData = createJavaVsyncEventData(env, vsyncEventData);
+        env->SetIntField(vsyncEventDataObj.get(),
+                         gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo
+                                 .preferredFrameTimelineIndex,
+                         vsyncEventData.preferredFrameTimelineIndex);
+        env->SetLongField(vsyncEventDataObj.get(),
+                          gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.frameInterval,
+                          vsyncEventData.frameInterval);
+
+        ScopedLocalRef<jobjectArray>
+                frameTimelinesObj(env,
+                                  reinterpret_cast<jobjectArray>(
+                                          env->GetObjectField(vsyncEventDataObj.get(),
+                                                              gDisplayEventReceiverClassInfo
+                                                                      .vsyncEventDataClassInfo
+                                                                      .frameTimelines)));
+        for (int i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) {
+            VsyncEventData::FrameTimeline& frameTimeline = vsyncEventData.frameTimelines[i];
+            ScopedLocalRef<jobject>
+                    frameTimelineObj(env, env->GetObjectArrayElement(frameTimelinesObj.get(), i));
+            env->SetLongField(frameTimelineObj.get(),
+                              gDisplayEventReceiverClassInfo.frameTimelineClassInfo.vsyncId,
+                              frameTimeline.vsyncId);
+            env->SetLongField(frameTimelineObj.get(),
+                              gDisplayEventReceiverClassInfo.frameTimelineClassInfo
+                                      .expectedPresentationTime,
+                              frameTimeline.expectedPresentationTime);
+            env->SetLongField(frameTimelineObj.get(),
+                              gDisplayEventReceiverClassInfo.frameTimelineClassInfo.deadline,
+                              frameTimeline.deadlineTimestamp);
+        }
+
         env->CallVoidMethod(receiverObj.get(), gDisplayEventReceiverClassInfo.dispatchVsync,
-                            timestamp, displayId.value, count, javaVsyncEventData);
+                            timestamp, displayId.value, count);
         ALOGV("receiver %p ~ Returned from vsync handler.", this);
     }
 
@@ -227,8 +271,9 @@
     mMessageQueue->raiseAndClearException(env, "dispatchModeChanged");
 }
 
-static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, jobject messageQueueObj,
-                        jint vsyncSource, jint eventRegistration, jlong layerHandle) {
+static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, jobject vsyncEventDataWeak,
+                        jobject messageQueueObj, jint vsyncSource, jint eventRegistration,
+                        jlong layerHandle) {
     sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
     if (messageQueue == NULL) {
         jniThrowRuntimeException(env, "MessageQueue is not initialized.");
@@ -236,8 +281,8 @@
     }
 
     sp<NativeDisplayEventReceiver> receiver =
-            new NativeDisplayEventReceiver(env, receiverWeak, messageQueue, vsyncSource,
-                                           eventRegistration, layerHandle);
+            new NativeDisplayEventReceiver(env, receiverWeak, vsyncEventDataWeak, messageQueue,
+                                           vsyncSource, eventRegistration, layerHandle);
     status_t status = receiver->initialize();
     if (status) {
         String8 message;
@@ -284,7 +329,9 @@
 
 static const JNINativeMethod gMethods[] = {
         /* name, signature, funcPtr */
-        {"nativeInit", "(Ljava/lang/ref/WeakReference;Landroid/os/MessageQueue;IIJ)J",
+        {"nativeInit",
+         "(Ljava/lang/ref/WeakReference;Ljava/lang/ref/WeakReference;Landroid/os/"
+         "MessageQueue;IIJ)J",
          (void*)nativeInit},
         {"nativeGetDisplayEventReceiverFinalizer", "()J",
          (void*)nativeGetDisplayEventReceiverFinalizer},
@@ -301,8 +348,7 @@
     gDisplayEventReceiverClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
 
     gDisplayEventReceiverClassInfo.dispatchVsync =
-            GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchVsync",
-                             "(JJILandroid/view/DisplayEventReceiver$VsyncEventData;)V");
+            GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchVsync", "(JJI)V");
     gDisplayEventReceiverClassInfo.dispatchHotplug = GetMethodIDOrDie(env,
             gDisplayEventReceiverClassInfo.clazz, "dispatchHotplug", "(JJZ)V");
     gDisplayEventReceiverClassInfo.dispatchModeChanged =
@@ -328,6 +374,15 @@
     gDisplayEventReceiverClassInfo.frameTimelineClassInfo.init =
             GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.frameTimelineClassInfo.clazz,
                              "<init>", "(JJJ)V");
+    gDisplayEventReceiverClassInfo.frameTimelineClassInfo.vsyncId =
+            GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.frameTimelineClassInfo.clazz,
+                            "vsyncId", "J");
+    gDisplayEventReceiverClassInfo.frameTimelineClassInfo.expectedPresentationTime =
+            GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.frameTimelineClassInfo.clazz,
+                            "expectedPresentationTime", "J");
+    gDisplayEventReceiverClassInfo.frameTimelineClassInfo.deadline =
+            GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.frameTimelineClassInfo.clazz,
+                            "deadline", "J");
 
     jclass vsyncEventDataClazz =
             FindClassOrDie(env, "android/view/DisplayEventReceiver$VsyncEventData");
@@ -339,6 +394,17 @@
                              "([Landroid/view/"
                              "DisplayEventReceiver$VsyncEventData$FrameTimeline;IJ)V");
 
+    gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.preferredFrameTimelineIndex =
+            GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz,
+                            "preferredFrameTimelineIndex", "I");
+    gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.frameInterval =
+            GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz,
+                            "frameInterval", "J");
+    gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.frameTimelines =
+            GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz,
+                            "frameTimelines",
+                            "[Landroid/view/DisplayEventReceiver$VsyncEventData$FrameTimeline;");
+
     return res;
 }
 
diff --git a/core/jni/com_android_internal_expresslog_Utils.cpp b/core/jni/com_android_internal_expresslog_Utils.cpp
deleted file mode 100644
index d33a7bd..0000000
--- a/core/jni/com_android_internal_expresslog_Utils.cpp
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <nativehelper/JNIHelp.h>
-#include <utils/hash/farmhash.h>
-
-#include "core_jni_helpers.h"
-
-// ----------------------------------------------------------------------------
-// JNI Glue
-// ----------------------------------------------------------------------------
-
-static jclass g_stringClass = nullptr;
-
-/**
- * Class:     com_android_internal_expresslog_Utils
- * Method:    hashString
- * Signature: (Ljava/lang/String;)J
- */
-static jlong hashString(JNIEnv* env, jclass /*class*/, jstring metricNameObj) {
-    ScopedUtfChars name(env, metricNameObj);
-    if (name.c_str() == nullptr) {
-        return 0;
-    }
-
-    return static_cast<jlong>(farmhash::Fingerprint64(name.c_str(), name.size()));
-}
-
-static const JNINativeMethod g_methods[] = {
-        {"hashString", "(Ljava/lang/String;)J", (void*)hashString},
-};
-
-static const char* const kUtilsPathName = "com/android/internal/expresslog/Utils";
-
-namespace android {
-
-int register_com_android_internal_expresslog_Utils(JNIEnv* env) {
-    jclass stringClass = FindClassOrDie(env, "java/lang/String");
-    g_stringClass = MakeGlobalRefOrDie(env, stringClass);
-
-    return RegisterMethodsOrDie(env, kUtilsPathName, g_methods, NELEM(g_methods));
-}
-
-} // namespace android
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index ed612a0..025a57d 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -1035,6 +1035,7 @@
 
             optional int32 uid = 1;
             repeated .android.app.ApplicationExitInfoProto app_exit_info = 2;
+            repeated .android.app.ApplicationExitInfoProto app_recoverable_crash = 3;
         }
         repeated User users = 2;
     }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 78d3923..05b38a5 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1746,37 +1746,6 @@
         android:protectionLevel="dangerous"
         android:permissionFlags="hardRestricted" />
 
-    <!-- @TestApi Allows an application to access wrist temperature data from the watch sensors.
-        <p class="note"><strong>Note: </strong> This permission is for Wear OS only.
-        <p>Protection level: dangerous
-        @hide
-        -->
-    <permission android:name="android.permission.BODY_SENSORS_WRIST_TEMPERATURE"
-                android:permissionGroup="android.permission-group.UNDEFINED"
-                android:label="@string/permlab_bodySensorsWristTemperature"
-                android:description="@string/permdesc_bodySensorsWristTemperature"
-                android:backgroundPermission="android.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND"
-                android:protectionLevel="dangerous" />
-
-    <!-- @TestApi Allows an application to access wrist temperature data from the watch sensors.
-         If you're requesting this permission, you must also request
-         {@link #BODY_SENSORS_WRIST_TEMPERATURE}. Requesting this permission by itself doesn't
-         give you wrist temperature body sensors access.
-         <p class="note"><strong>Note: </strong> This permission is for Wear OS only.
-         <p>Protection level: dangerous
-
-         <p> This is a hard restricted permission which cannot be held by an app until
-         the installer on record allowlists the permission. For more details see
-         {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
-         @hide
-    -->
-    <permission android:name="android.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND"
-                android:permissionGroup="android.permission-group.UNDEFINED"
-                android:label="@string/permlab_bodySensors_wristTemperature_background"
-                android:description="@string/permdesc_bodySensors_wristTemperature_background"
-                android:protectionLevel="dangerous"
-                android:permissionFlags="hardRestricted" />
-
     <!-- Allows an app to use fingerprint hardware.
          <p>Protection level: normal
          @deprecated Applications should request {@link
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e4d74b5..5544701 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -534,8 +534,10 @@
     <!-- If this is true, long press on power button will be available from the non-interactive state -->
     <bool name="config_supportLongPressPowerWhenNonInteractive">false</bool>
 
-    <!-- If this is true, then keep dreaming when undocking. -->
-    <bool name="config_keepDreamingWhenUndocking">false</bool>
+    <!-- If this is true, then keep dreaming when unplugging.
+         This config was formerly known as config_keepDreamingWhenUndocking.
+         It has been updated to affect other plug types. -->
+    <bool name="config_keepDreamingWhenUnplugging">false</bool>
 
     <!-- The timeout (in ms) to wait before attempting to reconnect to the dream overlay service if
          it becomes disconnected -->
@@ -1373,6 +1375,9 @@
     <!-- Number of notifications to keep in the notification service historical archive -->
     <integer name="config_notificationServiceArchiveSize">100</integer>
 
+    <!-- List of packages that will be able to use full screen intent in notifications by default -->
+    <string-array name="config_useFullScreenIntentPackages" translatable="false" />
+
     <!-- Allow the menu hard key to be disabled in LockScreen on some devices -->
     <bool name="config_disableMenuKeyInLockScreen">false</bool>
 
@@ -4301,8 +4306,12 @@
          This package must be trusted, as it has the permissions to control other applications
          on the device.
          Example: "com.android.wellbeing"
+
+         Note: This config is deprecated, please use config_systemWellbeing instead.
      -->
-    <string name="config_defaultWellbeingPackage" translatable="false"></string>
+    <string name="config_defaultWellbeingPackage" translatable="false">
+        @string/config_systemWellbeing
+    </string>
 
     <!-- The component name for the default system attention service.
          This service must be trusted, as it can be activated without explicit consent of the user.
diff --git a/core/res/res/values/public-final.xml b/core/res/res/values/public-final.xml
index 85325fe..daa0f553 100644
--- a/core/res/res/values/public-final.xml
+++ b/core/res/res/values/public-final.xml
@@ -3402,4 +3402,343 @@
     <!-- @hide @SystemApi -->
   <public type="bool" name="config_enableQrCodeScannerOnLockScreen" id="0x01110008" />
 
+  <!-- ===============================================================
+    Resources added in version U of the platform
+
+    NOTE: After this version of the platform is forked, changes cannot be made to the root
+    branch's groups for that release. Only merge changes to the forked platform branch.
+    =============================================================== -->
+  <eat-comment/>
+
+  <staging-public-group-final type="attr" first-id="0x01ce0000">
+    <public name="handwritingBoundsOffsetLeft" />
+    <public name="handwritingBoundsOffsetTop" />
+    <public name="handwritingBoundsOffsetRight" />
+    <public name="handwritingBoundsOffsetBottom" />
+    <public name="accessibilityDataSensitive" />
+    <public name="enableTextStylingShortcuts" />
+    <public name="requiredDisplayCategory"/>
+    <public name="removed_maxConcurrentSessionsCount" />
+    <public name="visualQueryDetectionService" />
+    <public name="physicalKeyboardHintLanguageTag" />
+    <public name="physicalKeyboardHintLayoutType" />
+    <public name="allowSharedIsolatedProcess" />
+    <public name="keyboardLocale" />
+    <public name="keyboardLayoutType" />
+    <public name="allowUpdateOwnership" />
+    <public name="isCredential"/>
+    <public name="searchResultHighlightColor" />
+    <public name="focusedSearchResultHighlightColor" />
+    <public name="stylusHandwritingSettingsActivity" />
+    <public name="windowNoMoveAnimation" />
+    <public name="settingsSubtitle" />
+    <public name="capability" />
+  </staging-public-group-final>
+
+  <public type="attr" name="handwritingBoundsOffsetLeft" id="0x01010673" />
+  <public type="attr" name="handwritingBoundsOffsetTop" id="0x01010674" />
+  <public type="attr" name="handwritingBoundsOffsetRight" id="0x01010675" />
+  <public type="attr" name="handwritingBoundsOffsetBottom" id="0x01010676" />
+  <public type="attr" name="accessibilityDataSensitive" id="0x01010677" />
+  <public type="attr" name="enableTextStylingShortcuts" id="0x01010678" />
+  <public type="attr" name="requiredDisplayCategory" id="0x01010679" />
+  <public type="attr" name="visualQueryDetectionService" id="0x0101067a" />
+  <public type="attr" name="physicalKeyboardHintLanguageTag" id="0x0101067b" />
+  <public type="attr" name="physicalKeyboardHintLayoutType" id="0x0101067c" />
+  <public type="attr" name="allowSharedIsolatedProcess" id="0x0101067d" />
+  <public type="attr" name="keyboardLocale" id="0x0101067e" />
+  <public type="attr" name="keyboardLayoutType" id="0x0101067f" />
+  <public type="attr" name="allowUpdateOwnership" id="0x01010680" />
+  <public type="attr" name="isCredential" id="0x01010681" />
+  <public type="attr" name="searchResultHighlightColor" id="0x01010682" />
+  <public type="attr" name="focusedSearchResultHighlightColor" id="0x01010683" />
+  <public type="attr" name="stylusHandwritingSettingsActivity" id="0x01010684" />
+  <public type="attr" name="windowNoMoveAnimation" id="0x01010685" />
+  <public type="attr" name="settingsSubtitle" id="0x01010686" />
+  <public type="attr" name="capability" id="0x01010687" />
+
+  <staging-public-group-final type="id" first-id="0x01cd0000">
+    <public name="bold" />
+    <public name="italic" />
+    <public name="underline" />
+    <public name="accessibilityActionScrollInDirection" />
+  </staging-public-group-final>
+
+  <public type="id" name="bold" id="0x0102005b" />
+  <public type="id" name="italic" id="0x0102005c" />
+  <public type="id" name="underline" id="0x0102005d" />
+  <public type="id" name="accessibilityActionScrollInDirection" id="0x0102005e" />
+
+  <staging-public-group-final type="string" first-id="0x01cb0000">
+    <!-- @hide @SystemApi -->
+    <public name="config_systemWearHealthService" />
+    <!-- @hide @SystemApi -->
+    <public name="config_defaultNotes" />
+    <!-- @hide @SystemApi -->
+    <public name="config_systemFinancedDeviceController" />
+    <!-- @hide @SystemApi -->
+    <public name="config_systemCallStreaming" />
+  </staging-public-group-final>
+
+    <!-- @hide @SystemApi -->
+  <public type="string" name="config_systemWearHealthService" id="0x01040044" />
+    <!-- @hide @SystemApi -->
+  <public type="string" name="config_defaultNotes" id="0x01040045" />
+    <!-- @hide @SystemApi -->
+  <public type="string" name="config_systemFinancedDeviceController" id="0x01040046" />
+    <!-- @hide @SystemApi -->
+  <public type="string" name="config_systemCallStreaming" id="0x01040047" />
+
+  <staging-public-group-final type="dimen" first-id="0x01ca0000">
+    <!-- @hide @SystemApi -->
+    <public name="config_viewConfigurationHandwritingGestureLineMargin" />
+  </staging-public-group-final>
+
+    <!-- @hide @SystemApi -->
+  <public type="dimen" name="config_viewConfigurationHandwritingGestureLineMargin" id="0x0105000a" />
+
+  <staging-public-group-final type="color" first-id="0x01c90000">
+    <public name="system_primary_container_light" />
+    <public name="system_on_primary_container_light" />
+    <public name="system_primary_light" />
+    <public name="system_on_primary_light" />
+    <public name="system_secondary_container_light" />
+    <public name="system_on_secondary_container_light" />
+    <public name="system_secondary_light" />
+    <public name="system_on_secondary_light" />
+    <public name="system_tertiary_container_light" />
+    <public name="system_on_tertiary_container_light" />
+    <public name="system_tertiary_light" />
+    <public name="system_on_tertiary_light" />
+    <public name="system_background_light" />
+    <public name="system_on_background_light" />
+    <public name="system_surface_light" />
+    <public name="system_on_surface_light" />
+    <public name="system_surface_container_low_light" />
+    <public name="system_surface_container_lowest_light" />
+    <public name="system_surface_container_light" />
+    <public name="system_surface_container_high_light" />
+    <public name="system_surface_container_highest_light" />
+    <public name="system_surface_bright_light" />
+    <public name="system_surface_dim_light" />
+    <public name="system_surface_variant_light" />
+    <public name="system_on_surface_variant_light" />
+    <public name="system_outline_light" />
+    <public name="system_error_light" />
+    <public name="system_on_error_light" />
+    <public name="system_error_container_light" />
+    <public name="system_on_error_container_light" />
+    <public name="removed_system_primary_fixed_light" />
+    <public name="removed_system_primary_fixed_dim_light" />
+    <public name="removed_system_on_primary_fixed_light" />
+    <public name="removed_system_on_primary_fixed_variant_light" />
+    <public name="removed_system_secondary_fixed_light" />
+    <public name="removed_system_secondary_fixed_dim_light" />
+    <public name="removed_system_on_secondary_fixed_light" />
+    <public name="removed_system_on_secondary_fixed_variant_light" />
+    <public name="removed_system_tertiary_fixed_light" />
+    <public name="removed_system_tertiary_fixed_dim_light" />
+    <public name="removed_system_on_tertiary_fixed_light" />
+    <public name="removed_system_on_tertiary_fixed_variant_light" />
+    <public name="system_control_activated_light" />
+    <public name="system_control_normal_light" />
+    <public name="system_control_highlight_light" />
+    <public name="system_text_primary_inverse_light" />
+    <public name="system_text_secondary_and_tertiary_inverse_light" />
+    <public name="system_text_primary_inverse_disable_only_light" />
+    <public name="system_text_secondary_and_tertiary_inverse_disabled_light" />
+    <public name="system_text_hint_inverse_light" />
+    <public name="system_palette_key_color_primary_light" />
+    <public name="system_palette_key_color_secondary_light" />
+    <public name="system_palette_key_color_tertiary_light" />
+    <public name="system_palette_key_color_neutral_light" />
+    <public name="system_palette_key_color_neutral_variant_light" />
+    <public name="system_primary_container_dark"/>
+    <public name="system_on_primary_container_dark"/>
+    <public name="system_primary_dark"/>
+    <public name="system_on_primary_dark"/>
+    <public name="system_secondary_container_dark"/>
+    <public name="system_on_secondary_container_dark"/>
+    <public name="system_secondary_dark"/>
+    <public name="system_on_secondary_dark"/>
+    <public name="system_tertiary_container_dark"/>
+    <public name="system_on_tertiary_container_dark"/>
+    <public name="system_tertiary_dark"/>
+    <public name="system_on_tertiary_dark"/>
+    <public name="system_background_dark"/>
+    <public name="system_on_background_dark"/>
+    <public name="system_surface_dark"/>
+    <public name="system_on_surface_dark"/>
+    <public name="system_surface_container_low_dark"/>
+    <public name="system_surface_container_lowest_dark"/>
+    <public name="system_surface_container_dark"/>
+    <public name="system_surface_container_high_dark"/>
+    <public name="system_surface_container_highest_dark"/>
+    <public name="system_surface_bright_dark"/>
+    <public name="system_surface_dim_dark"/>
+    <public name="system_surface_variant_dark"/>
+    <public name="system_on_surface_variant_dark"/>
+    <public name="system_outline_dark"/>
+    <public name="system_error_dark"/>
+    <public name="system_on_error_dark"/>
+    <public name="system_error_container_dark"/>
+    <public name="system_on_error_container_dark"/>
+    <public name="removed_system_primary_fixed_dark"/>
+    <public name="removed_system_primary_fixed_dim_dark"/>
+    <public name="removed_system_on_primary_fixed_dark"/>
+    <public name="removed_system_on_primary_fixed_variant_dark"/>
+    <public name="removed_system_secondary_fixed_dark"/>
+    <public name="removed_system_secondary_fixed_dim_dark"/>
+    <public name="removed_system_on_secondary_fixed_dark"/>
+    <public name="removed_system_on_secondary_fixed_variant_dark"/>
+    <public name="removed_system_tertiary_fixed_dark"/>
+    <public name="removed_system_tertiary_fixed_dim_dark"/>
+    <public name="removed_system_on_tertiary_fixed_dark"/>
+    <public name="removed_system_on_tertiary_fixed_variant_dark"/>
+    <public name="system_control_activated_dark"/>
+    <public name="system_control_normal_dark"/>
+    <public name="system_control_highlight_dark"/>
+    <public name="system_text_primary_inverse_dark"/>
+    <public name="system_text_secondary_and_tertiary_inverse_dark"/>
+    <public name="system_text_primary_inverse_disable_only_dark"/>
+    <public name="system_text_secondary_and_tertiary_inverse_disabled_dark"/>
+    <public name="system_text_hint_inverse_dark"/>
+    <public name="system_palette_key_color_primary_dark"/>
+    <public name="system_palette_key_color_secondary_dark"/>
+    <public name="system_palette_key_color_tertiary_dark"/>
+    <public name="system_palette_key_color_neutral_dark"/>
+    <public name="system_palette_key_color_neutral_variant_dark"/>
+    <public name="system_primary_fixed" />
+    <public name="system_primary_fixed_dim" />
+    <public name="system_on_primary_fixed" />
+    <public name="system_on_primary_fixed_variant" />
+    <public name="system_secondary_fixed" />
+    <public name="system_secondary_fixed_dim" />
+    <public name="system_on_secondary_fixed" />
+    <public name="system_on_secondary_fixed_variant" />
+    <public name="system_tertiary_fixed" />
+    <public name="system_tertiary_fixed_dim" />
+    <public name="system_on_tertiary_fixed" />
+    <public name="system_on_tertiary_fixed_variant" />
+    <public name="system_outline_variant_light" />
+    <public name="system_outline_variant_dark" />
+  </staging-public-group-final>
+
+  <public type="color" name="system_primary_container_light" id="0x0106005e" />
+  <public type="color" name="system_on_primary_container_light" id="0x0106005f" />
+  <public type="color" name="system_primary_light" id="0x01060060" />
+  <public type="color" name="system_on_primary_light" id="0x01060061" />
+  <public type="color" name="system_secondary_container_light" id="0x01060062" />
+  <public type="color" name="system_on_secondary_container_light" id="0x01060063" />
+  <public type="color" name="system_secondary_light" id="0x01060064" />
+  <public type="color" name="system_on_secondary_light" id="0x01060065" />
+  <public type="color" name="system_tertiary_container_light" id="0x01060066" />
+  <public type="color" name="system_on_tertiary_container_light" id="0x01060067" />
+  <public type="color" name="system_tertiary_light" id="0x01060068" />
+  <public type="color" name="system_on_tertiary_light" id="0x01060069" />
+  <public type="color" name="system_background_light" id="0x0106006a" />
+  <public type="color" name="system_on_background_light" id="0x0106006b" />
+  <public type="color" name="system_surface_light" id="0x0106006c" />
+  <public type="color" name="system_on_surface_light" id="0x0106006d" />
+  <public type="color" name="system_surface_container_low_light" id="0x0106006e" />
+  <public type="color" name="system_surface_container_lowest_light" id="0x0106006f" />
+  <public type="color" name="system_surface_container_light" id="0x01060070" />
+  <public type="color" name="system_surface_container_high_light" id="0x01060071" />
+  <public type="color" name="system_surface_container_highest_light" id="0x01060072" />
+  <public type="color" name="system_surface_bright_light" id="0x01060073" />
+  <public type="color" name="system_surface_dim_light" id="0x01060074" />
+  <public type="color" name="system_surface_variant_light" id="0x01060075" />
+  <public type="color" name="system_on_surface_variant_light" id="0x01060076" />
+  <public type="color" name="system_outline_light" id="0x01060077" />
+  <public type="color" name="system_error_light" id="0x01060078" />
+  <public type="color" name="system_on_error_light" id="0x01060079" />
+  <public type="color" name="system_error_container_light" id="0x0106007a" />
+  <public type="color" name="system_on_error_container_light" id="0x0106007b" />
+  <public type="color" name="system_control_activated_light" id="0x0106007c" />
+  <public type="color" name="system_control_normal_light" id="0x0106007d" />
+  <public type="color" name="system_control_highlight_light" id="0x0106007e" />
+  <public type="color" name="system_text_primary_inverse_light" id="0x0106007f" />
+  <public type="color" name="system_text_secondary_and_tertiary_inverse_light" id="0x01060080" />
+  <public type="color" name="system_text_primary_inverse_disable_only_light" id="0x01060081" />
+  <public type="color" name="system_text_secondary_and_tertiary_inverse_disabled_light" id="0x01060082" />
+  <public type="color" name="system_text_hint_inverse_light" id="0x01060083" />
+  <public type="color" name="system_palette_key_color_primary_light" id="0x01060084" />
+  <public type="color" name="system_palette_key_color_secondary_light" id="0x01060085" />
+  <public type="color" name="system_palette_key_color_tertiary_light" id="0x01060086" />
+  <public type="color" name="system_palette_key_color_neutral_light" id="0x01060087" />
+  <public type="color" name="system_palette_key_color_neutral_variant_light" id="0x01060088" />
+  <public type="color" name="system_primary_container_dark" id="0x01060089" />
+  <public type="color" name="system_on_primary_container_dark" id="0x0106008a" />
+  <public type="color" name="system_primary_dark" id="0x0106008b" />
+  <public type="color" name="system_on_primary_dark" id="0x0106008c" />
+  <public type="color" name="system_secondary_container_dark" id="0x0106008d" />
+  <public type="color" name="system_on_secondary_container_dark" id="0x0106008e" />
+  <public type="color" name="system_secondary_dark" id="0x0106008f" />
+  <public type="color" name="system_on_secondary_dark" id="0x01060090" />
+  <public type="color" name="system_tertiary_container_dark" id="0x01060091" />
+  <public type="color" name="system_on_tertiary_container_dark" id="0x01060092" />
+  <public type="color" name="system_tertiary_dark" id="0x01060093" />
+  <public type="color" name="system_on_tertiary_dark" id="0x01060094" />
+  <public type="color" name="system_background_dark" id="0x01060095" />
+  <public type="color" name="system_on_background_dark" id="0x01060096" />
+  <public type="color" name="system_surface_dark" id="0x01060097" />
+  <public type="color" name="system_on_surface_dark" id="0x01060098" />
+  <public type="color" name="system_surface_container_low_dark" id="0x01060099" />
+  <public type="color" name="system_surface_container_lowest_dark" id="0x0106009a" />
+  <public type="color" name="system_surface_container_dark" id="0x0106009b" />
+  <public type="color" name="system_surface_container_high_dark" id="0x0106009c" />
+  <public type="color" name="system_surface_container_highest_dark" id="0x0106009d" />
+  <public type="color" name="system_surface_bright_dark" id="0x0106009e" />
+  <public type="color" name="system_surface_dim_dark" id="0x0106009f" />
+  <public type="color" name="system_surface_variant_dark" id="0x010600a0" />
+  <public type="color" name="system_on_surface_variant_dark" id="0x010600a1" />
+  <public type="color" name="system_outline_dark" id="0x010600a2" />
+  <public type="color" name="system_error_dark" id="0x010600a3" />
+  <public type="color" name="system_on_error_dark" id="0x010600a4" />
+  <public type="color" name="system_error_container_dark" id="0x010600a5" />
+  <public type="color" name="system_on_error_container_dark" id="0x010600a6" />
+  <public type="color" name="system_control_activated_dark" id="0x010600a7" />
+  <public type="color" name="system_control_normal_dark" id="0x010600a8" />
+  <public type="color" name="system_control_highlight_dark" id="0x010600a9" />
+  <public type="color" name="system_text_primary_inverse_dark" id="0x010600aa" />
+  <public type="color" name="system_text_secondary_and_tertiary_inverse_dark" id="0x010600ab" />
+  <public type="color" name="system_text_primary_inverse_disable_only_dark" id="0x010600ac" />
+  <public type="color" name="system_text_secondary_and_tertiary_inverse_disabled_dark" id="0x010600ad" />
+  <public type="color" name="system_text_hint_inverse_dark" id="0x010600ae" />
+  <public type="color" name="system_palette_key_color_primary_dark" id="0x010600af" />
+  <public type="color" name="system_palette_key_color_secondary_dark" id="0x010600b0" />
+  <public type="color" name="system_palette_key_color_tertiary_dark" id="0x010600b1" />
+  <public type="color" name="system_palette_key_color_neutral_dark" id="0x010600b2" />
+  <public type="color" name="system_palette_key_color_neutral_variant_dark" id="0x010600b3" />
+  <public type="color" name="system_primary_fixed" id="0x010600b4" />
+  <public type="color" name="system_primary_fixed_dim" id="0x010600b5" />
+  <public type="color" name="system_on_primary_fixed" id="0x010600b6" />
+  <public type="color" name="system_on_primary_fixed_variant" id="0x010600b7" />
+  <public type="color" name="system_secondary_fixed" id="0x010600b8" />
+  <public type="color" name="system_secondary_fixed_dim" id="0x010600b9" />
+  <public type="color" name="system_on_secondary_fixed" id="0x010600ba" />
+  <public type="color" name="system_on_secondary_fixed_variant" id="0x010600bb" />
+  <public type="color" name="system_tertiary_fixed" id="0x010600bc" />
+  <public type="color" name="system_tertiary_fixed_dim" id="0x010600bd" />
+  <public type="color" name="system_on_tertiary_fixed" id="0x010600be" />
+  <public type="color" name="system_on_tertiary_fixed_variant" id="0x010600bf" />
+  <public type="color" name="system_outline_variant_light" id="0x010600c0" />
+  <public type="color" name="system_outline_variant_dark" id="0x010600c1" />
+
+  <staging-public-group-final type="bool" first-id="0x01be0000">
+    <!-- @hide @SystemApi -->
+    <public name="config_safetyProtectionEnabled" />
+    <!-- @hide @SystemApi -->
+    <public name="config_enableDefaultNotes" />
+    <!-- @hide @SystemApi -->
+    <public name="config_enableDefaultNotesForWorkProfile" />
+  </staging-public-group-final>
+
+    <!-- @hide @SystemApi -->
+  <public type="bool" name="config_safetyProtectionEnabled" id="0x01110009" />
+    <!-- @hide @SystemApi -->
+  <public type="bool" name="config_enableDefaultNotes" id="0x0111000a" />
+    <!-- @hide @SystemApi -->
+  <public type="bool" name="config_enableDefaultNotesForWorkProfile" id="0x0111000b" />
+
 </resources>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 9cbf3b6..49a1940 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -102,231 +102,65 @@
 <resources>
 
   <!-- ===============================================================
-    Resources added in version U of the platform
+    Resources added in version NEXT of the platform
 
     NOTE: After this version of the platform is forked, changes cannot be made to the root
     branch's groups for that release. Only merge changes to the forked platform branch.
     =============================================================== -->
   <eat-comment/>
 
-  <staging-public-group type="attr" first-id="0x01ce0000">
-    <public name="handwritingBoundsOffsetLeft" />
-    <public name="handwritingBoundsOffsetTop" />
-    <public name="handwritingBoundsOffsetRight" />
-    <public name="handwritingBoundsOffsetBottom" />
-    <public name="accessibilityDataSensitive" />
-    <public name="enableTextStylingShortcuts" />
-    <public name="requiredDisplayCategory"/>
-    <public name="removed_maxConcurrentSessionsCount" />
-    <public name="visualQueryDetectionService" />
-    <public name="physicalKeyboardHintLanguageTag" />
-    <public name="physicalKeyboardHintLayoutType" />
-    <public name="allowSharedIsolatedProcess" />
-    <public name="keyboardLocale" />
-    <public name="keyboardLayoutType" />
-    <public name="allowUpdateOwnership" />
-    <public name="isCredential"/>
-    <public name="searchResultHighlightColor" />
-    <public name="focusedSearchResultHighlightColor" />
-    <public name="stylusHandwritingSettingsActivity" />
-    <public name="windowNoMoveAnimation" />
-    <public name="settingsSubtitle" />
-    <public name="capability" />
+  <staging-public-group type="attr" first-id="0x01bd0000">
   </staging-public-group>
 
-  <staging-public-group type="id" first-id="0x01cd0000">
-    <public name="bold" />
-    <public name="italic" />
-    <public name="underline" />
-    <public name="accessibilityActionScrollInDirection" />
+  <staging-public-group type="id" first-id="0x01bc0000">
   </staging-public-group>
 
-  <staging-public-group type="style" first-id="0x01cc0000">
+  <staging-public-group type="style" first-id="0x01bb0000">
   </staging-public-group>
 
-  <staging-public-group type="string" first-id="0x01cb0000">
-    <!-- @hide @SystemApi -->
-    <public name="config_systemWearHealthService" />
-    <!-- @hide @SystemApi -->
-    <public name="config_defaultNotes" />
-    <!-- @hide @SystemApi -->
-    <public name="config_systemFinancedDeviceController" />
-    <!-- @hide @SystemApi -->
-    <public name="config_systemCallStreaming" />
+  <staging-public-group type="string" first-id="0x01ba0000">
   </staging-public-group>
 
-  <staging-public-group type="dimen" first-id="0x01ca0000">
-    <!-- @hide @SystemApi -->
-    <public name="config_viewConfigurationHandwritingGestureLineMargin" />
+  <staging-public-group type="dimen" first-id="0x01b90000">
   </staging-public-group>
 
-  <staging-public-group type="color" first-id="0x01c90000">
-    <public name="system_primary_container_light" />
-    <public name="system_on_primary_container_light" />
-    <public name="system_primary_light" />
-    <public name="system_on_primary_light" />
-    <public name="system_secondary_container_light" />
-    <public name="system_on_secondary_container_light" />
-    <public name="system_secondary_light" />
-    <public name="system_on_secondary_light" />
-    <public name="system_tertiary_container_light" />
-    <public name="system_on_tertiary_container_light" />
-    <public name="system_tertiary_light" />
-    <public name="system_on_tertiary_light" />
-    <public name="system_background_light" />
-    <public name="system_on_background_light" />
-    <public name="system_surface_light" />
-    <public name="system_on_surface_light" />
-    <public name="system_surface_container_low_light" />
-    <public name="system_surface_container_lowest_light" />
-    <public name="system_surface_container_light" />
-    <public name="system_surface_container_high_light" />
-    <public name="system_surface_container_highest_light" />
-    <public name="system_surface_bright_light" />
-    <public name="system_surface_dim_light" />
-    <public name="system_surface_variant_light" />
-    <public name="system_on_surface_variant_light" />
-    <public name="system_outline_light" />
-    <public name="system_error_light" />
-    <public name="system_on_error_light" />
-    <public name="system_error_container_light" />
-    <public name="system_on_error_container_light" />
-    <public name="removed_system_primary_fixed_light" />
-    <public name="removed_system_primary_fixed_dim_light" />
-    <public name="removed_system_on_primary_fixed_light" />
-    <public name="removed_system_on_primary_fixed_variant_light" />
-    <public name="removed_system_secondary_fixed_light" />
-    <public name="removed_system_secondary_fixed_dim_light" />
-    <public name="removed_system_on_secondary_fixed_light" />
-    <public name="removed_system_on_secondary_fixed_variant_light" />
-    <public name="removed_system_tertiary_fixed_light" />
-    <public name="removed_system_tertiary_fixed_dim_light" />
-    <public name="removed_system_on_tertiary_fixed_light" />
-    <public name="removed_system_on_tertiary_fixed_variant_light" />
-    <public name="system_control_activated_light" />
-    <public name="system_control_normal_light" />
-    <public name="system_control_highlight_light" />
-    <public name="system_text_primary_inverse_light" />
-    <public name="system_text_secondary_and_tertiary_inverse_light" />
-    <public name="system_text_primary_inverse_disable_only_light" />
-    <public name="system_text_secondary_and_tertiary_inverse_disabled_light" />
-    <public name="system_text_hint_inverse_light" />
-    <public name="system_palette_key_color_primary_light" />
-    <public name="system_palette_key_color_secondary_light" />
-    <public name="system_palette_key_color_tertiary_light" />
-    <public name="system_palette_key_color_neutral_light" />
-    <public name="system_palette_key_color_neutral_variant_light" />
-    <public name="system_primary_container_dark"/>
-    <public name="system_on_primary_container_dark"/>
-    <public name="system_primary_dark"/>
-    <public name="system_on_primary_dark"/>
-    <public name="system_secondary_container_dark"/>
-    <public name="system_on_secondary_container_dark"/>
-    <public name="system_secondary_dark"/>
-    <public name="system_on_secondary_dark"/>
-    <public name="system_tertiary_container_dark"/>
-    <public name="system_on_tertiary_container_dark"/>
-    <public name="system_tertiary_dark"/>
-    <public name="system_on_tertiary_dark"/>
-    <public name="system_background_dark"/>
-    <public name="system_on_background_dark"/>
-    <public name="system_surface_dark"/>
-    <public name="system_on_surface_dark"/>
-    <public name="system_surface_container_low_dark"/>
-    <public name="system_surface_container_lowest_dark"/>
-    <public name="system_surface_container_dark"/>
-    <public name="system_surface_container_high_dark"/>
-    <public name="system_surface_container_highest_dark"/>
-    <public name="system_surface_bright_dark"/>
-    <public name="system_surface_dim_dark"/>
-    <public name="system_surface_variant_dark"/>
-    <public name="system_on_surface_variant_dark"/>
-    <public name="system_outline_dark"/>
-    <public name="system_error_dark"/>
-    <public name="system_on_error_dark"/>
-    <public name="system_error_container_dark"/>
-    <public name="system_on_error_container_dark"/>
-    <public name="removed_system_primary_fixed_dark"/>
-    <public name="removed_system_primary_fixed_dim_dark"/>
-    <public name="removed_system_on_primary_fixed_dark"/>
-    <public name="removed_system_on_primary_fixed_variant_dark"/>
-    <public name="removed_system_secondary_fixed_dark"/>
-    <public name="removed_system_secondary_fixed_dim_dark"/>
-    <public name="removed_system_on_secondary_fixed_dark"/>
-    <public name="removed_system_on_secondary_fixed_variant_dark"/>
-    <public name="removed_system_tertiary_fixed_dark"/>
-    <public name="removed_system_tertiary_fixed_dim_dark"/>
-    <public name="removed_system_on_tertiary_fixed_dark"/>
-    <public name="removed_system_on_tertiary_fixed_variant_dark"/>
-    <public name="system_control_activated_dark"/>
-    <public name="system_control_normal_dark"/>
-    <public name="system_control_highlight_dark"/>
-    <public name="system_text_primary_inverse_dark"/>
-    <public name="system_text_secondary_and_tertiary_inverse_dark"/>
-    <public name="system_text_primary_inverse_disable_only_dark"/>
-    <public name="system_text_secondary_and_tertiary_inverse_disabled_dark"/>
-    <public name="system_text_hint_inverse_dark"/>
-    <public name="system_palette_key_color_primary_dark"/>
-    <public name="system_palette_key_color_secondary_dark"/>
-    <public name="system_palette_key_color_tertiary_dark"/>
-    <public name="system_palette_key_color_neutral_dark"/>
-    <public name="system_palette_key_color_neutral_variant_dark"/>
-    <public name="system_primary_fixed" />
-    <public name="system_primary_fixed_dim" />
-    <public name="system_on_primary_fixed" />
-    <public name="system_on_primary_fixed_variant" />
-    <public name="system_secondary_fixed" />
-    <public name="system_secondary_fixed_dim" />
-    <public name="system_on_secondary_fixed" />
-    <public name="system_on_secondary_fixed_variant" />
-    <public name="system_tertiary_fixed" />
-    <public name="system_tertiary_fixed_dim" />
-    <public name="system_on_tertiary_fixed" />
-    <public name="system_on_tertiary_fixed_variant" />
-    <public name="system_outline_variant_light" />
-    <public name="system_outline_variant_dark" />
+  <staging-public-group type="color" first-id="0x01b80000">
   </staging-public-group>
 
-  <staging-public-group type="array" first-id="0x01c80000">
+  <staging-public-group type="array" first-id="0x01b70000">
   </staging-public-group>
 
-  <staging-public-group type="drawable" first-id="0x01c70000">
+  <staging-public-group type="drawable" first-id="0x01b60000">
   </staging-public-group>
 
-  <staging-public-group type="layout" first-id="0x01c60000">
+  <staging-public-group type="layout" first-id="0x01b50000">
   </staging-public-group>
 
-  <staging-public-group type="anim" first-id="0x01c50000">
+  <staging-public-group type="anim" first-id="0x01b40000">
   </staging-public-group>
 
-  <staging-public-group type="animator" first-id="0x01c40000">
+  <staging-public-group type="animator" first-id="0x01b30000">
   </staging-public-group>
 
-  <staging-public-group type="interpolator" first-id="0x01c30000">
+  <staging-public-group type="interpolator" first-id="0x01b20000">
   </staging-public-group>
 
-  <staging-public-group type="mipmap" first-id="0x01c20000">
+  <staging-public-group type="mipmap" first-id="0x01b10000">
   </staging-public-group>
 
-  <staging-public-group type="integer" first-id="0x01c10000">
+  <staging-public-group type="integer" first-id="0x01b00000">
   </staging-public-group>
 
-  <staging-public-group type="transition" first-id="0x01c00000">
+  <staging-public-group type="transition" first-id="0x01af0000">
   </staging-public-group>
 
-  <staging-public-group type="raw" first-id="0x01bf0000">
+  <staging-public-group type="raw" first-id="0x01ae0000">
   </staging-public-group>
 
-  <staging-public-group type="bool" first-id="0x01be0000">
-    <!-- @hide @SystemApi -->
-    <public name="config_safetyProtectionEnabled" />
-    <!-- @hide @SystemApi -->
-    <public name="config_enableDefaultNotes" />
-    <!-- @hide @SystemApi -->
-    <public name="config_enableDefaultNotesForWorkProfile" />
+  <staging-public-group type="bool" first-id="0x01ad0000">
   </staging-public-group>
 
-  <staging-public-group type="fraction" first-id="0x01bd0000">
+  <staging-public-group type="fraction" first-id="0x01ac0000">
   </staging-public-group>
 
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 8231407..947dc2d 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1347,16 +1347,6 @@
     <!-- Description of the background body sensors permission, listed so the user can decide whether to allow the application to access data from body sensors in the background. [CHAR LIMIT=NONE] -->
     <string name="permdesc_bodySensors_background" product="default">Allows the app to access body sensor data, such as heart rate, temperature, and blood oxygen percentage, while the app is in the background.</string>
 
-    <!-- Title of the body sensors wrist temperature permission, listed so the user can decide whether to allow the application to access body sensor wrist temperature data. [CHAR LIMIT=NONE] -->
-    <string name="permlab_bodySensorsWristTemperature">Access body sensor wrist temperature data while the app is in use.</string>
-    <!-- Description of the body sensors wrist temperature permission, listed so the user can decide whether to allow the application to access data from body sensors. [CHAR LIMIT=NONE] -->
-    <string name="permdesc_bodySensorsWristTemperature" product="default">Allows the app to access body sensor wrist temperature data, while the app is in use.</string>
-
-    <!-- Title of the body sensors wrist temperature permission, listed so the user can decide whether to allow the application to access body sensor wrist temperature data. [CHAR LIMIT=NONE] -->
-    <string name="permlab_bodySensors_wristTemperature_background">Access body sensor wrist temperature data while the app is in the background.</string>
-    <!-- Description of the body sensors wrist temperature permission, listed so the user can decide whether to allow the application to access data from body sensors. [CHAR LIMIT=NONE] -->
-    <string name="permdesc_bodySensors_wristTemperature_background" product="default">Allows the app to access body sensor wrist temperature data, while the app is in the background.</string>
-
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_readCalendar">Read calendar events and details</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c6c1c8f..cea6032 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1944,7 +1944,7 @@
   <java-symbol type="bool" name="config_allowTheaterModeWakeFromLidSwitch" />
   <java-symbol type="bool" name="config_allowTheaterModeWakeFromDock" />
   <java-symbol type="bool" name="config_allowTheaterModeWakeFromWindowLayout" />
-  <java-symbol type="bool" name="config_keepDreamingWhenUndocking" />
+  <java-symbol type="bool" name="config_keepDreamingWhenUnplugging" />
   <java-symbol type="integer" name="config_keyguardDrawnTimeout" />
   <java-symbol type="bool" name="config_goToSleepOnButtonPressTheaterMode" />
   <java-symbol type="bool" name="config_supportLongPressPowerWhenNonInteractive" />
@@ -2017,6 +2017,7 @@
   <java-symbol type="integer" name="config_notificationsBatteryMediumARGB" />
   <java-symbol type="integer" name="config_notificationsBatteryNearlyFullLevel" />
   <java-symbol type="integer" name="config_notificationServiceArchiveSize" />
+  <java-symbol type="array" name="config_useFullScreenIntentPackages" />
   <java-symbol type="integer" name="config_previousVibrationsDumpLimit" />
   <java-symbol type="integer" name="config_defaultVibrationAmplitude" />
   <java-symbol type="dimen" name="config_hapticChannelMaxVibrationAmplitude" />
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index e59b259..7068453 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -1665,47 +1665,47 @@
 
         <item name="materialColorOnSecondaryFixedVariant">@color/system_on_secondary_fixed_variant</item>
         <item name="materialColorOnTertiaryFixedVariant">@color/system_on_tertiary_fixed_variant</item>
-        <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_dark</item>
+        <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_light</item>
         <item name="materialColorOnPrimaryFixedVariant">@color/system_on_primary_fixed_variant</item>
-        <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_dark</item>
-        <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_dark</item>
-        <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_dark</item>
-        <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_dark</item>
+        <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_light</item>
+        <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_light</item>
+        <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_light</item>
+        <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_light</item>
         <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
-        <item name="materialColorOnErrorContainer">@color/system_on_error_container_dark</item>
+        <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item>
         <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
-        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
+        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item>
         <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
         <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
         <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
-        <item name="materialColorSecondaryContainer">@color/system_secondary_container_dark</item>
-        <item name="materialColorErrorContainer">@color/system_error_container_dark</item>
+        <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item>
+        <item name="materialColorErrorContainer">@color/system_error_container_light</item>
         <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
-        <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
+        <item name="materialColorPrimaryInverse">@color/system_primary_dark</item>
         <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
-        <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
-        <item name="materialColorSurfaceVariant">@color/system_surface_variant_dark</item>
-        <item name="materialColorTertiaryContainer">@color/system_tertiary_container_dark</item>
+        <item name="materialColorSurfaceInverse">@color/system_surface_dark</item>
+        <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item>
+        <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item>
         <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
-        <item name="materialColorPrimaryContainer">@color/system_primary_container_dark</item>
-        <item name="materialColorOnBackground">@color/system_on_background_dark</item>
+        <item name="materialColorPrimaryContainer">@color/system_primary_container_light</item>
+        <item name="materialColorOnBackground">@color/system_on_background_light</item>
         <item name="materialColorPrimaryFixed">@color/system_primary_fixed</item>
-        <item name="materialColorOnSecondary">@color/system_on_secondary_dark</item>
-        <item name="materialColorOnTertiary">@color/system_on_tertiary_dark</item>
-        <item name="materialColorSurfaceDim">@color/system_surface_dim_dark</item>
-        <item name="materialColorSurfaceBright">@color/system_surface_bright_dark</item>
-        <item name="materialColorSecondary">@color/system_secondary_dark</item>
-        <item name="materialColorOnError">@color/system_on_error_dark</item>
-        <item name="materialColorSurface">@color/system_surface_dark</item>
-        <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_dark</item>
-        <item name="materialColorTertiary">@color/system_tertiary_dark</item>
-        <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
-        <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
-        <item name="materialColorOutline">@color/system_outline_dark</item>
-        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
-        <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
-        <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
-        <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
+        <item name="materialColorOnSecondary">@color/system_on_secondary_light</item>
+        <item name="materialColorOnTertiary">@color/system_on_tertiary_light</item>
+        <item name="materialColorSurfaceDim">@color/system_surface_dim_light</item>
+        <item name="materialColorSurfaceBright">@color/system_surface_bright_light</item>
+        <item name="materialColorSecondary">@color/system_secondary_light</item>
+        <item name="materialColorOnError">@color/system_on_error_light</item>
+        <item name="materialColorSurface">@color/system_surface_light</item>
+        <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_light</item>
+        <item name="materialColorTertiary">@color/system_tertiary_light</item>
+        <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
+        <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
+        <item name="materialColorOutline">@color/system_outline_light</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
+        <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
+        <item name="materialColorOnSurface">@color/system_on_surface_light</item>
+        <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
     </style>
 
     <!-- DeviceDefault style for input methods, which is used by the
@@ -1758,47 +1758,47 @@
 
         <item name="materialColorOnSecondaryFixedVariant">@color/system_on_secondary_fixed_variant</item>
         <item name="materialColorOnTertiaryFixedVariant">@color/system_on_tertiary_fixed_variant</item>
-        <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_dark</item>
+        <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_light</item>
         <item name="materialColorOnPrimaryFixedVariant">@color/system_on_primary_fixed_variant</item>
-        <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_dark</item>
-        <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_dark</item>
-        <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_dark</item>
-        <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_dark</item>
+        <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_light</item>
+        <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_light</item>
+        <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_light</item>
+        <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_light</item>
         <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
-        <item name="materialColorOnErrorContainer">@color/system_on_error_container_dark</item>
+        <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item>
         <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
-        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
+        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item>
         <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
         <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
         <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
-        <item name="materialColorSecondaryContainer">@color/system_secondary_container_dark</item>
-        <item name="materialColorErrorContainer">@color/system_error_container_dark</item>
+        <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item>
+        <item name="materialColorErrorContainer">@color/system_error_container_light</item>
         <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
-        <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
+        <item name="materialColorPrimaryInverse">@color/system_primary_dark</item>
         <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
-        <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
-        <item name="materialColorSurfaceVariant">@color/system_surface_variant_dark</item>
-        <item name="materialColorTertiaryContainer">@color/system_tertiary_container_dark</item>
+        <item name="materialColorSurfaceInverse">@color/system_surface_dark</item>
+        <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item>
+        <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item>
         <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
-        <item name="materialColorPrimaryContainer">@color/system_primary_container_dark</item>
-        <item name="materialColorOnBackground">@color/system_on_background_dark</item>
+        <item name="materialColorPrimaryContainer">@color/system_primary_container_light</item>
+        <item name="materialColorOnBackground">@color/system_on_background_light</item>
         <item name="materialColorPrimaryFixed">@color/system_primary_fixed</item>
-        <item name="materialColorOnSecondary">@color/system_on_secondary_dark</item>
-        <item name="materialColorOnTertiary">@color/system_on_tertiary_dark</item>
-        <item name="materialColorSurfaceDim">@color/system_surface_dim_dark</item>
-        <item name="materialColorSurfaceBright">@color/system_surface_bright_dark</item>
-        <item name="materialColorSecondary">@color/system_secondary_dark</item>
-        <item name="materialColorOnError">@color/system_on_error_dark</item>
-        <item name="materialColorSurface">@color/system_surface_dark</item>
-        <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_dark</item>
-        <item name="materialColorTertiary">@color/system_tertiary_dark</item>
-        <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
-        <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
-        <item name="materialColorOutline">@color/system_outline_dark</item>
-        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
-        <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
-        <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
-        <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
+        <item name="materialColorOnSecondary">@color/system_on_secondary_light</item>
+        <item name="materialColorOnTertiary">@color/system_on_tertiary_light</item>
+        <item name="materialColorSurfaceDim">@color/system_surface_dim_light</item>
+        <item name="materialColorSurfaceBright">@color/system_surface_bright_light</item>
+        <item name="materialColorSecondary">@color/system_secondary_light</item>
+        <item name="materialColorOnError">@color/system_on_error_light</item>
+        <item name="materialColorSurface">@color/system_surface_light</item>
+        <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_light</item>
+        <item name="materialColorTertiary">@color/system_tertiary_light</item>
+        <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
+        <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
+        <item name="materialColorOutline">@color/system_outline_light</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
+        <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
+        <item name="materialColorOnSurface">@color/system_on_surface_light</item>
+        <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
     </style>
 
     <style name="Theme.DeviceDefault.Dialog.Alert" parent="Theme.Material.Dialog.Alert">
@@ -4043,16 +4043,16 @@
         <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
         <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item>
         <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
-        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
+        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item>
         <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
         <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
         <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
         <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item>
         <item name="materialColorErrorContainer">@color/system_error_container_light</item>
         <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
-        <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
+        <item name="materialColorPrimaryInverse">@color/system_primary_dark</item>
         <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
-        <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
+        <item name="materialColorSurfaceInverse">@color/system_surface_dark</item>
         <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item>
         <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item>
         <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
@@ -4123,16 +4123,16 @@
         <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
         <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item>
         <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
-        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
+        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item>
         <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
         <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
         <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
         <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item>
         <item name="materialColorErrorContainer">@color/system_error_container_light</item>
         <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
-        <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
+        <item name="materialColorPrimaryInverse">@color/system_primary_dark</item>
         <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
-        <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
+        <item name="materialColorSurfaceInverse">@color/system_surface_dark</item>
         <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item>
         <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item>
         <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
@@ -4195,16 +4195,16 @@
         <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
         <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item>
         <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
-        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
+        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item>
         <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
         <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
         <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
         <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item>
         <item name="materialColorErrorContainer">@color/system_error_container_light</item>
         <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
-        <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
+        <item name="materialColorPrimaryInverse">@color/system_primary_dark</item>
         <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
-        <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
+        <item name="materialColorSurfaceInverse">@color/system_surface_dark</item>
         <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item>
         <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item>
         <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
@@ -4366,16 +4366,16 @@
         <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
         <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item>
         <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
-        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
+        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item>
         <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
         <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
         <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
         <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item>
         <item name="materialColorErrorContainer">@color/system_error_container_light</item>
         <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
-        <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
+        <item name="materialColorPrimaryInverse">@color/system_primary_dark</item>
         <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
-        <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
+        <item name="materialColorSurfaceInverse">@color/system_surface_dark</item>
         <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item>
         <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item>
         <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
@@ -4475,47 +4475,47 @@
 
         <item name="materialColorOnSecondaryFixedVariant">@color/system_on_secondary_fixed_variant</item>
         <item name="materialColorOnTertiaryFixedVariant">@color/system_on_tertiary_fixed_variant</item>
-        <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_dark</item>
+        <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_light</item>
         <item name="materialColorOnPrimaryFixedVariant">@color/system_on_primary_fixed_variant</item>
-        <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_dark</item>
-        <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_dark</item>
-        <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_dark</item>
-        <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_dark</item>
+        <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_light</item>
+        <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_light</item>
+        <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_light</item>
+        <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_light</item>
         <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
-        <item name="materialColorOnErrorContainer">@color/system_on_error_container_dark</item>
+        <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item>
         <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
-        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
+        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item>
         <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
         <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
         <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
-        <item name="materialColorSecondaryContainer">@color/system_secondary_container_dark</item>
-        <item name="materialColorErrorContainer">@color/system_error_container_dark</item>
+        <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item>
+        <item name="materialColorErrorContainer">@color/system_error_container_light</item>
         <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
-        <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
+        <item name="materialColorPrimaryInverse">@color/system_primary_dark</item>
         <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
-        <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
-        <item name="materialColorSurfaceVariant">@color/system_surface_variant_dark</item>
-        <item name="materialColorTertiaryContainer">@color/system_tertiary_container_dark</item>
+        <item name="materialColorSurfaceInverse">@color/system_surface_dark</item>
+        <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item>
+        <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item>
         <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
-        <item name="materialColorPrimaryContainer">@color/system_primary_container_dark</item>
-        <item name="materialColorOnBackground">@color/system_on_background_dark</item>
+        <item name="materialColorPrimaryContainer">@color/system_primary_container_light</item>
+        <item name="materialColorOnBackground">@color/system_on_background_light</item>
         <item name="materialColorPrimaryFixed">@color/system_primary_fixed</item>
-        <item name="materialColorOnSecondary">@color/system_on_secondary_dark</item>
-        <item name="materialColorOnTertiary">@color/system_on_tertiary_dark</item>
-        <item name="materialColorSurfaceDim">@color/system_surface_dim_dark</item>
-        <item name="materialColorSurfaceBright">@color/system_surface_bright_dark</item>
-        <item name="materialColorSecondary">@color/system_secondary_dark</item>
-        <item name="materialColorOnError">@color/system_on_error_dark</item>
-        <item name="materialColorSurface">@color/system_surface_dark</item>
-        <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_dark</item>
-        <item name="materialColorTertiary">@color/system_tertiary_dark</item>
-        <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
-        <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
-        <item name="materialColorOutline">@color/system_outline_dark</item>
-        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
-        <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
-        <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
-        <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
+        <item name="materialColorOnSecondary">@color/system_on_secondary_light</item>
+        <item name="materialColorOnTertiary">@color/system_on_tertiary_light</item>
+        <item name="materialColorSurfaceDim">@color/system_surface_dim_light</item>
+        <item name="materialColorSurfaceBright">@color/system_surface_bright_light</item>
+        <item name="materialColorSecondary">@color/system_secondary_light</item>
+        <item name="materialColorOnError">@color/system_on_error_light</item>
+        <item name="materialColorSurface">@color/system_surface_light</item>
+        <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_light</item>
+        <item name="materialColorTertiary">@color/system_tertiary_light</item>
+        <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
+        <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
+        <item name="materialColorOutline">@color/system_outline_light</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
+        <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
+        <item name="materialColorOnSurface">@color/system_on_surface_light</item>
+        <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
     </style>
 
     <style name="Theme.DeviceDefault.Settings.Dialog.Alert" parent="Theme.Material.Settings.Dialog.Alert">
@@ -4570,47 +4570,47 @@
 
         <item name="materialColorOnSecondaryFixedVariant">@color/system_on_secondary_fixed_variant</item>
         <item name="materialColorOnTertiaryFixedVariant">@color/system_on_tertiary_fixed_variant</item>
-        <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_dark</item>
+        <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_light</item>
         <item name="materialColorOnPrimaryFixedVariant">@color/system_on_primary_fixed_variant</item>
-        <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_dark</item>
-        <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_dark</item>
-        <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_dark</item>
-        <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_dark</item>
+        <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_light</item>
+        <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_light</item>
+        <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_light</item>
+        <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_light</item>
         <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
-        <item name="materialColorOnErrorContainer">@color/system_on_error_container_dark</item>
+        <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item>
         <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
-        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
+        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item>
         <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
         <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
         <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
-        <item name="materialColorSecondaryContainer">@color/system_secondary_container_dark</item>
-        <item name="materialColorErrorContainer">@color/system_error_container_dark</item>
+        <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item>
+        <item name="materialColorErrorContainer">@color/system_error_container_light</item>
         <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
-        <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
+        <item name="materialColorPrimaryInverse">@color/system_primary_dark</item>
         <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
-        <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
-        <item name="materialColorSurfaceVariant">@color/system_surface_variant_dark</item>
-        <item name="materialColorTertiaryContainer">@color/system_tertiary_container_dark</item>
+        <item name="materialColorSurfaceInverse">@color/system_surface_dark</item>
+        <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item>
+        <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item>
         <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
-        <item name="materialColorPrimaryContainer">@color/system_primary_container_dark</item>
-        <item name="materialColorOnBackground">@color/system_on_background_dark</item>
+        <item name="materialColorPrimaryContainer">@color/system_primary_container_light</item>
+        <item name="materialColorOnBackground">@color/system_on_background_light</item>
         <item name="materialColorPrimaryFixed">@color/system_primary_fixed</item>
-        <item name="materialColorOnSecondary">@color/system_on_secondary_dark</item>
-        <item name="materialColorOnTertiary">@color/system_on_tertiary_dark</item>
-        <item name="materialColorSurfaceDim">@color/system_surface_dim_dark</item>
-        <item name="materialColorSurfaceBright">@color/system_surface_bright_dark</item>
-        <item name="materialColorSecondary">@color/system_secondary_dark</item>
-        <item name="materialColorOnError">@color/system_on_error_dark</item>
-        <item name="materialColorSurface">@color/system_surface_dark</item>
-        <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_dark</item>
-        <item name="materialColorTertiary">@color/system_tertiary_dark</item>
-        <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
-        <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
-        <item name="materialColorOutline">@color/system_outline_dark</item>
-        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
-        <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
-        <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
-        <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
+        <item name="materialColorOnSecondary">@color/system_on_secondary_light</item>
+        <item name="materialColorOnTertiary">@color/system_on_tertiary_light</item>
+        <item name="materialColorSurfaceDim">@color/system_surface_dim_light</item>
+        <item name="materialColorSurfaceBright">@color/system_surface_bright_light</item>
+        <item name="materialColorSecondary">@color/system_secondary_light</item>
+        <item name="materialColorOnError">@color/system_on_error_light</item>
+        <item name="materialColorSurface">@color/system_surface_light</item>
+        <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_light</item>
+        <item name="materialColorTertiary">@color/system_tertiary_light</item>
+        <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
+        <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
+        <item name="materialColorOutline">@color/system_outline_light</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
+        <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
+        <item name="materialColorOnSurface">@color/system_on_surface_light</item>
+        <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
     </style>
 
     <style name="Theme.DeviceDefault.Settings.Dialog.NoActionBar" parent="Theme.DeviceDefault.Light.Dialog.NoActionBar" />
@@ -4700,16 +4700,16 @@
         <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
         <item name="materialColorOnErrorContainer">@color/system_on_error_container_dark</item>
         <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
-        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item>
+        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
         <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
         <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
         <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
         <item name="materialColorSecondaryContainer">@color/system_secondary_container_dark</item>
         <item name="materialColorErrorContainer">@color/system_error_container_dark</item>
         <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
-        <item name="materialColorPrimaryInverse">@color/system_primary_dark</item>
+        <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
         <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
-        <item name="materialColorSurfaceInverse">@color/system_surface_dark</item>
+        <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
         <item name="materialColorSurfaceVariant">@color/system_surface_variant_dark</item>
         <item name="materialColorTertiaryContainer">@color/system_tertiary_container_dark</item>
         <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
diff --git a/core/res/res/xml/irq_device_map.xml b/core/res/res/xml/irq_device_map.xml
index 4fae8fb..8b3667e 100644
--- a/core/res/res/xml/irq_device_map.xml
+++ b/core/res/res/xml/irq_device_map.xml
@@ -18,14 +18,16 @@
 -->
 <irq-device-map>
   <!--  This file maps devices (chips) that can send interrupts to the main processor (and bring it
-        out of sleep) to logical subsystems in userspace code. Since each Android device has its own
-        uniquely designed chipset, this mapping is expected to be empty by default and should be
-        overridden by device-specific configs.
+        out of sleep) to logical subsystems in userspace code. Since each Android device can have
+        a differently designed chipset, this mapping is expected to be empty by default and should
+        be overridden by device-specific configs.
         This mapping helps the system to meaningfully attribute CPU wakeups to logical work that
-        happened on the device. The devices are referred to by their names as defined in the kernel.
-        Currently, defined subsystems are:
-        - Alarm: Use this to denote wakeup alarms requested by apps via the AlarmManager API.
-        - Wifi: Use this to denote network traffic that uses the wifi transport.
+        happened on the device and the app activity that caused it. The devices are referred to by
+        their names as defined in the kernel. Currently, defined subsystems are:
+            - Alarm: Use this to denote wakeup alarms requested by apps via the AlarmManager API.
+            - Wifi: Use this to denote network traffic that uses the wifi transport.
+            - Sound_trigger: Use this to denote sound phrase detection, like the ones supported by
+        SoundTriggerManager.
 
         The overlay should use tags <device> and <subsystem> to describe this mapping in the
         following way:
diff --git a/core/tests/coretests/src/android/app/OWNERS b/core/tests/coretests/src/android/app/OWNERS
index b3f3993..8d9461d 100644
--- a/core/tests/coretests/src/android/app/OWNERS
+++ b/core/tests/coretests/src/android/app/OWNERS
@@ -4,3 +4,6 @@
 per-file *Notification* = file:/packages/SystemUI/OWNERS
 per-file *Zen* = file:/packages/SystemUI/OWNERS
 per-file *StatusBar* = file:/packages/SystemUI/OWNERS
+
+# A11Y and related
+per-file *UiAutomation* = file:/services/accessibility/OWNERS
diff --git a/core/tests/coretests/src/android/app/UiAutomationTest.java b/core/tests/coretests/src/android/app/UiAutomationTest.java
new file mode 100644
index 0000000..3ac5057
--- /dev/null
+++ b/core/tests/coretests/src/android/app/UiAutomationTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.Looper;
+import android.os.UserManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public final class UiAutomationTest {
+
+    private static final int SECONDARY_DISPLAY_ID = 42;
+    private static final int DISPLAY_ID_ASSIGNED_TO_USER = 108;
+
+    private final Looper mLooper = Looper.getMainLooper();
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private UserManager mUserManager;
+    @Mock
+    private IUiAutomationConnection mConnection;
+
+    @Before
+    public void setFixtures() {
+        when(mContext.getMainLooper()).thenReturn(mLooper);
+        mockSystemService(UserManager.class, mUserManager);
+
+        // Set default expectations
+        mockVisibleBackgroundUsersSupported(/* supported= */ false);
+        mockUserVisibility(/* visible= */ true);
+        // make sure it's not used, unless explicitly mocked
+        mockDisplayAssignedToUser(INVALID_DISPLAY);
+        mockContextDisplay(DEFAULT_DISPLAY);
+    }
+
+    @Test
+    public void testContextConstructor_nullContext() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new UiAutomation((Context) null, mConnection));
+    }
+
+    @Test
+    public void testContextConstructor_nullConnection() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new UiAutomation(mContext, (IUiAutomationConnection) null));
+    }
+
+    @Test
+    public void testGetDisplay_contextWithSecondaryDisplayId() {
+        mockContextDisplay(SECONDARY_DISPLAY_ID);
+
+        UiAutomation uiAutomation = new UiAutomation(mContext, mConnection);
+
+        // It's always DEFAULT_DISPLAY regardless, unless the device supports visible bg users
+        assertWithMessage("getDisplayId()").that(uiAutomation.getDisplayId())
+                .isEqualTo(DEFAULT_DISPLAY);
+    }
+
+    @Test
+    public void testGetDisplay_contextWithInvalidDisplayId() {
+        mockContextDisplay(INVALID_DISPLAY);
+
+        UiAutomation uiAutomation = new UiAutomation(mContext, mConnection);
+
+        assertWithMessage("getDisplayId()").that(uiAutomation.getDisplayId())
+                .isEqualTo(DEFAULT_DISPLAY);
+    }
+
+    @Test
+    public void testGetDisplay_visibleBgUsers() {
+        mockVisibleBackgroundUsersSupported(/* supported= */ true);
+        mockContextDisplay(SECONDARY_DISPLAY_ID);
+        // Should be using display from context, not from user
+        mockDisplayAssignedToUser(DISPLAY_ID_ASSIGNED_TO_USER);
+
+        UiAutomation uiAutomation = new UiAutomation(mContext, mConnection);
+
+        assertWithMessage("getDisplayId()").that(uiAutomation.getDisplayId())
+                .isEqualTo(SECONDARY_DISPLAY_ID);
+    }
+
+    @Test
+    public void testGetDisplay_visibleBgUsers_contextWithInvalidDisplayId() {
+        mockVisibleBackgroundUsersSupported(/* supported= */ true);
+        mockContextDisplay(INVALID_DISPLAY);
+        mockDisplayAssignedToUser(DISPLAY_ID_ASSIGNED_TO_USER);
+
+        UiAutomation uiAutomation = new UiAutomation(mContext, mConnection);
+
+        assertWithMessage("getDisplayId()").that(uiAutomation.getDisplayId())
+                .isEqualTo(DISPLAY_ID_ASSIGNED_TO_USER);
+    }
+
+    private <T> void mockSystemService(Class<T> svcClass, T svc) {
+        String svcName = svcClass.getName();
+        when(mContext.getSystemServiceName(svcClass)).thenReturn(svcName);
+        when(mContext.getSystemService(svcName)).thenReturn(svc);
+    }
+
+    private void mockVisibleBackgroundUsersSupported(boolean supported) {
+        when(mUserManager.isVisibleBackgroundUsersSupported()).thenReturn(supported);
+    }
+
+    private void mockContextDisplay(int displayId) {
+        when(mContext.getDisplayId()).thenReturn(displayId);
+    }
+
+    private void mockDisplayAssignedToUser(int displayId) {
+        when(mUserManager.getMainDisplayIdAssignedToUser()).thenReturn(displayId);
+    }
+
+    private void mockUserVisibility(boolean visible) {
+        when(mUserManager.isUserVisible()).thenReturn(visible);
+    }
+}
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
index 980211f..c6bb07b 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
@@ -25,6 +25,7 @@
 import android.app.Activity;
 import android.compat.testing.PlatformCompatChangeRule;
 import android.os.Bundle;
+import android.platform.test.annotations.IwTest;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
 import android.util.PollingCheck;
@@ -84,6 +85,7 @@
         }
     }
 
+    @IwTest(focusArea = "accessibility")
     @Test
     public void testFontsScaleNonLinearly() {
         final ActivityScenario<TestActivity> scenario = rule.getScenario();
@@ -114,6 +116,7 @@
         )));
     }
 
+    @IwTest(focusArea = "accessibility")
     @Test
     public void testOnConfigurationChanged_doesNotCrash() {
         final ActivityScenario<TestActivity> scenario = rule.getScenario();
@@ -127,6 +130,7 @@
         });
     }
 
+    @IwTest(focusArea = "accessibility")
     @Test
     public void testUpdateConfiguration_doesNotCrash() {
         final ActivityScenario<TestActivity> scenario = rule.getScenario();
diff --git a/core/tests/coretests/src/android/content/res/TEST_MAPPING b/core/tests/coretests/src/android/content/res/TEST_MAPPING
index 4ea6e40..ab14950 100644
--- a/core/tests/coretests/src/android/content/res/TEST_MAPPING
+++ b/core/tests/coretests/src/android/content/res/TEST_MAPPING
@@ -39,5 +39,18 @@
         }
       ]
     }
+  ],
+  "ironwood-postsubmit": [
+    {
+      "name": "FrameworksCoreTests",
+      "options":[
+        {
+            "include-annotation": "android.platform.test.annotations.IwTest"
+        },
+        {
+            "exclude-annotation": "org.junit.Ignore"
+        }
+      ]
+    }
   ]
 }
diff --git a/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java b/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java
new file mode 100644
index 0000000..66f3bca
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.biometrics;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.junit.MockitoRule;
+
+import java.util.concurrent.Executor;
+
+
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class BiometricPromptTest {
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private IAuthService mService;
+    private BiometricPrompt mBiometricPrompt;
+
+    private CancellationSignal mCancellationSignal;
+
+    private final TestLooper mLooper = new TestLooper();
+    private final Handler mHandler = new Handler(mLooper.getLooper());
+    private final Executor mExecutor = mHandler::post;
+
+    @Before
+    public void setUp() throws RemoteException {
+        mBiometricPrompt = new BiometricPrompt.Builder(mContext)
+                .setUseDefaultSubtitle()
+                .setUseDefaultTitle()
+                .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG
+                        | BiometricManager.Authenticators.DEVICE_CREDENTIAL)
+                .setService(mService)
+                .build();
+
+        mCancellationSignal = new CancellationSignal();
+        when(mService.authenticate(any(), anyLong(), anyInt(), any(), anyString(), any()))
+                .thenReturn(0L);
+        when(mContext.getPackageName()).thenReturn("BiometricPromptTest");
+    }
+
+    @Test
+    public void testCancellationAfterAuthenticationFailed() throws RemoteException {
+        ArgumentCaptor<IBiometricServiceReceiver> biometricServiceReceiverCaptor =
+                ArgumentCaptor.forClass(IBiometricServiceReceiver.class);
+        BiometricPrompt.AuthenticationCallback callback =
+                new BiometricPrompt.AuthenticationCallback() {
+            @Override
+            public void onAuthenticationError(int errorCode, CharSequence errString) {
+                super.onAuthenticationError(errorCode, errString);
+            }};
+        mBiometricPrompt.authenticate(mCancellationSignal, mExecutor, callback);
+        mLooper.dispatchAll();
+
+        verify(mService).authenticate(any(), anyLong(), anyInt(),
+                biometricServiceReceiverCaptor.capture(), anyString(), any());
+
+        biometricServiceReceiverCaptor.getValue().onAuthenticationFailed();
+        mLooper.dispatchAll();
+        mCancellationSignal.cancel();
+
+        verify(mService).cancelAuthentication(any(), anyString(), anyLong());
+    }
+}
diff --git a/core/tests/coretests/src/android/hardware/biometrics/OWNERS b/core/tests/coretests/src/android/hardware/biometrics/OWNERS
new file mode 100644
index 0000000..6a2192a
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/biometrics/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/biometrics/OWNERS
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index 3b6e8ea..cde100c 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -70,7 +70,13 @@
     private ApplicationInfo mApplicationInfo;
 
     private final BackMotionEvent mBackEvent = new BackMotionEvent(
-            0, 0, 0, BackEvent.EDGE_LEFT, null);
+            /* touchX = */ 0,
+            /* touchY = */ 0,
+            /* progress = */ 0,
+            /* velocityX = */ 0,
+            /* velocityY = */ 0,
+            /* swipeEdge = */ BackEvent.EDGE_LEFT,
+            /* departingAnimationTarget = */ null);
 
     @Before
     public void setUp() throws Exception {
diff --git a/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java
index 4716312..36c2a62 100644
--- a/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java
@@ -23,16 +23,25 @@
 import static com.android.internal.util.DumpUtils.isPlatformNonCriticalPackage;
 import static com.android.internal.util.DumpUtils.isPlatformPackage;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import android.content.ComponentName;
+import android.util.SparseArray;
 
 import junit.framework.TestCase;
 
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
 /**
  * Run with:
  atest FrameworksCoreTests:DumpUtilsTest
  */
 public class DumpUtilsTest extends TestCase {
 
+    private final StringWriter mStringWriter = new StringWriter();
+    private final PrintWriter mPrintWriter = new PrintWriter(mStringWriter);
+
     private static ComponentName cn(String componentName) {
         if (componentName == null) {
             return null;
@@ -168,4 +177,144 @@
                 Integer.toHexString(System.identityHashCode(component))).test(
                         wcn("com.google/.abc")));
     }
+
+    public void testDumpSparseArray_empty() {
+        SparseArray<String> array = new SparseArray<>();
+
+        DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ "...", array, "whatever");
+
+        String output = flushPrintWriter();
+
+        assertWithMessage("empty array dump").that(output).isEqualTo("...No whatevers\n");
+    }
+
+    public void testDumpSparseArray_oneElement() {
+        SparseArray<String> array = new SparseArray<>();
+        array.put(1, "uno");
+
+        DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number");
+
+        String output = flushPrintWriter();
+
+        assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+                + ".1 number(s):\n"
+                + "..0: 1->uno\n");
+    }
+
+    public void testDumpSparseArray_oneNullElement() {
+        SparseArray<String> array = new SparseArray<>();
+        array.put(1, null);
+
+        DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "NULL");
+
+        String output = flushPrintWriter();
+
+        assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+                + ".1 NULL(s):\n"
+                + "..0: 1->(null)\n");
+    }
+
+    public void testDumpSparseArray_multipleElements() {
+        SparseArray<String> array = new SparseArray<>();
+        array.put(1, "uno");
+        array.put(2, "duo");
+        array.put(42, null);
+
+        DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number");
+
+        String output = flushPrintWriter();
+
+        assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+                + ".3 number(s):\n"
+                + "..0: 1->uno\n"
+                + "..1: 2->duo\n"
+                + "..2: 42->(null)\n");
+    }
+
+    public void testDumpSparseArray_keyDumperOnly() {
+        SparseArray<String> array = new SparseArray<>();
+        array.put(1, "uno");
+        array.put(2, "duo");
+        array.put(42, null);
+
+        DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number",
+                (i, k) -> {
+                    mPrintWriter.printf("_%d=%d_", i, k);
+                }, /* valueDumper= */ null);
+
+        String output = flushPrintWriter();
+
+        assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+                + ".3 number(s):\n"
+                + "_0=1_uno\n"
+                + "_1=2_duo\n"
+                + "_2=42_(null)\n");
+    }
+
+    public void testDumpSparseArray_valueDumperOnly() {
+        SparseArray<String> array = new SparseArray<>();
+        array.put(1, "uno");
+        array.put(2, "duo");
+        array.put(42, null);
+
+        DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number",
+                /* keyDumper= */ null,
+                s -> {
+                    mPrintWriter.print(s.toUpperCase());
+                });
+
+        String output = flushPrintWriter();
+
+        assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+                + ".3 number(s):\n"
+                + "..0: 1->UNO\n"
+                + "..1: 2->DUO\n"
+                + "..2: 42->(null)\n");
+    }
+
+    public void testDumpSparseArray_keyAndValueDumpers() {
+        SparseArray<String> array = new SparseArray<>();
+        array.put(1, "uno");
+        array.put(2, "duo");
+        array.put(42, null);
+
+        DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number",
+                (i, k) -> {
+                    mPrintWriter.printf("_%d=%d_", i, k);
+                },
+                s -> {
+                    mPrintWriter.print(s.toUpperCase());
+                });
+
+        String output = flushPrintWriter();
+
+        assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+                + ".3 number(s):\n"
+                + "_0=1_UNO\n"
+                + "_1=2_DUO\n"
+                + "_2=42_(null)\n");
+    }
+
+    public void testDumpSparseArrayValues() {
+        SparseArray<String> array = new SparseArray<>();
+        array.put(1, "uno");
+        array.put(2, "duo");
+        array.put(42, null);
+
+        DumpUtils.dumpSparseArrayValues(mPrintWriter, /* prefix= */ ".", array, "number");
+
+        String output = flushPrintWriter();
+
+        assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+                + ".3 numbers:\n"
+                + "..uno\n"
+                + "..duo\n"
+                + "..(null)\n");
+    }
+
+    private String flushPrintWriter() {
+        mPrintWriter.flush();
+
+        return mStringWriter.toString();
+    }
 }
diff --git a/core/tests/expresslog/AndroidManifest.xml b/core/tests/expresslog/AndroidManifest.xml
deleted file mode 100644
index 94a39e0..0000000
--- a/core/tests/expresslog/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          android:installLocation="internalOnly"
-          package="com.android.internal.expresslog" >
-
-    <application >
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:targetPackage="com.android.internal.expresslog"
-            android:label="Telemetry Express Logging Helper Tests" />
-
-</manifest>
diff --git a/core/tests/expresslog/OWNERS b/core/tests/expresslog/OWNERS
deleted file mode 100644
index 3dc958b..0000000
--- a/core/tests/expresslog/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-# Bug component: 719316
-# Stats/expresslog
-file:/services/core/java/com/android/server/stats/OWNERS
diff --git a/core/tests/expresslog/TEST_MAPPING b/core/tests/expresslog/TEST_MAPPING
deleted file mode 100644
index c9b0cf8..0000000
--- a/core/tests/expresslog/TEST_MAPPING
+++ /dev/null
@@ -1,12 +0,0 @@
-{
-  "presubmit": [
-    {
-      "name": "ExpressLogTests",
-      "options": [
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
-      ]
-    }
-  ]
-}
\ No newline at end of file
diff --git a/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java b/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java
deleted file mode 100644
index ee62d75..0000000
--- a/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java
+++ /dev/null
@@ -1,167 +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.internal.expresslog;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-@SmallTest
-public class ScaledRangeOptionsTest {
-    private static final String TAG = ScaledRangeOptionsTest.class.getSimpleName();
-
-    @Test
-    public void testGetBinsCount() {
-        Histogram.ScaledRangeOptions options1 = new Histogram.ScaledRangeOptions(1, 100, 100, 2);
-        assertEquals(3, options1.getBinsCount());
-
-        Histogram.ScaledRangeOptions options10 = new Histogram.ScaledRangeOptions(10, 100, 100, 2);
-        assertEquals(12, options10.getBinsCount());
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructZeroBinsCount() {
-        new Histogram.ScaledRangeOptions(0, 100, 100, 2);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructNegativeBinsCount() {
-        new Histogram.ScaledRangeOptions(-1, 100, 100, 2);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructNegativeFirstBinWidth() {
-        new Histogram.ScaledRangeOptions(10, 100, -100, 2);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructTooSmallFirstBinWidth() {
-        new Histogram.ScaledRangeOptions(10, 100, 0.5f, 2);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructNegativeScaleFactor() {
-        new Histogram.ScaledRangeOptions(10, 100, 100, -2);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructTooSmallScaleFactor() {
-        new Histogram.ScaledRangeOptions(10, 100, 100, 0.5f);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructTooBigScaleFactor() {
-        new Histogram.ScaledRangeOptions(10, 100, 100, 500.f);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructTooBigBinRange() {
-        new Histogram.ScaledRangeOptions(100, 100, 100, 10.f);
-    }
-
-    @Test
-    public void testBinIndexForRangeEqual1() {
-        Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 1, 1);
-        assertEquals(12, options.getBinsCount());
-
-        assertEquals(11, options.getBinForSample(11));
-
-        for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
-            assertEquals(i, options.getBinForSample(i));
-        }
-    }
-
-    @Test
-    public void testBinIndexForRangeEqual2() {
-        // this should produce bin otpions similar to linear histogram with bin width 2
-        Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 2, 1);
-        assertEquals(12, options.getBinsCount());
-
-        for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
-            assertEquals(i, options.getBinForSample(i * 2));
-            assertEquals(i, options.getBinForSample(i * 2 - 1));
-        }
-    }
-
-    @Test
-    public void testBinIndexForRangeEqual5() {
-        Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(2, 0, 5, 1);
-        assertEquals(4, options.getBinsCount());
-        for (int i = 0; i < 2; i++) {
-            for (int sample = 0; sample < 5; sample++) {
-                assertEquals(i + 1, options.getBinForSample(i * 5 + sample));
-            }
-        }
-    }
-
-    @Test
-    public void testBinIndexForRangeEqual10() {
-        Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 10, 1);
-        assertEquals(0, options.getBinForSample(0));
-        assertEquals(options.getBinsCount() - 2, options.getBinForSample(100));
-        assertEquals(options.getBinsCount() - 1, options.getBinForSample(101));
-
-        final float binSize = (101 - 1) / 10f;
-        for (int i = 1, bins = options.getBinsCount() - 1; i < bins; i++) {
-            assertEquals(i, options.getBinForSample(i * binSize));
-        }
-    }
-
-    @Test
-    public void testBinIndexForScaleFactor2() {
-        final int binsCount = 10;
-        final int minValue = 10;
-        final int firstBinWidth = 5;
-        final int scaledFactor = 2;
-
-        Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(
-                binsCount, minValue, firstBinWidth, scaledFactor);
-        assertEquals(binsCount + 2, options.getBinsCount());
-        long[] binCounts = new long[10];
-
-        // precalculate max valid value - start value for the overflow bin
-        int lastBinStartValue = minValue; //firstBinMin value
-        int lastBinWidth = firstBinWidth;
-        for (int binIdx = 2; binIdx <= binsCount + 1; binIdx++) {
-            lastBinStartValue = lastBinStartValue + lastBinWidth;
-            lastBinWidth *= scaledFactor;
-        }
-
-        // underflow bin
-        for (int i = 1; i < minValue; i++) {
-            assertEquals(0, options.getBinForSample(i));
-        }
-
-        for (int i = 10; i < lastBinStartValue; i++) {
-            assertTrue(options.getBinForSample(i) > 0);
-            assertTrue(options.getBinForSample(i) <= binsCount);
-            binCounts[options.getBinForSample(i) - 1]++;
-        }
-
-        // overflow bin
-        assertEquals(binsCount + 1, options.getBinForSample(lastBinStartValue));
-
-        for (int i = 1; i < binsCount; i++) {
-            assertEquals(binCounts[i], binCounts[i - 1] * 2L);
-        }
-    }
-}
diff --git a/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java b/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java
deleted file mode 100644
index 037dbb3..0000000
--- a/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java
+++ /dev/null
@@ -1,125 +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.internal.expresslog;
-
-import androidx.test.filters.SmallTest;
-
-import static org.junit.Assert.assertEquals;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-@SmallTest
-public class UniformOptionsTest {
-    private static final String TAG = UniformOptionsTest.class.getSimpleName();
-
-    @Test
-    public void testGetBinsCount() {
-        Histogram.UniformOptions options1 = new Histogram.UniformOptions(1, 100, 1000);
-        assertEquals(3, options1.getBinsCount());
-
-        Histogram.UniformOptions options10 = new Histogram.UniformOptions(10, 100, 1000);
-        assertEquals(12, options10.getBinsCount());
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructZeroBinsCount() {
-        new Histogram.UniformOptions(0, 100, 1000);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructNegativeBinsCount() {
-        new Histogram.UniformOptions(-1, 100, 1000);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testConstructMaxValueLessThanMinValue() {
-        new Histogram.UniformOptions(10, 1000, 100);
-    }
-
-    @Test
-    public void testBinIndexForRangeEqual1() {
-        Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 11);
-        for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
-            assertEquals(i, options.getBinForSample(i));
-        }
-    }
-
-    @Test
-    public void testBinIndexForRangeEqual2() {
-        Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 21);
-        for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
-            assertEquals(i, options.getBinForSample(i * 2));
-            assertEquals(i, options.getBinForSample(i * 2 - 1));
-        }
-    }
-
-    @Test
-    public void testBinIndexForRangeEqual5() {
-        Histogram.UniformOptions options = new Histogram.UniformOptions(2, 0, 10);
-        assertEquals(4, options.getBinsCount());
-        for (int i = 0; i < 2; i++) {
-            for (int sample = 0; sample < 5; sample++) {
-                assertEquals(i + 1, options.getBinForSample(i * 5 + sample));
-            }
-        }
-    }
-
-    @Test
-    public void testBinIndexForRangeEqual10() {
-        Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 101);
-        assertEquals(0, options.getBinForSample(0));
-        assertEquals(options.getBinsCount() - 2, options.getBinForSample(100));
-        assertEquals(options.getBinsCount() - 1, options.getBinForSample(101));
-
-        final float binSize = (101 - 1) / 10f;
-        for (int i = 1, bins = options.getBinsCount() - 1; i < bins; i++) {
-            assertEquals(i, options.getBinForSample(i * binSize));
-        }
-    }
-
-    @Test
-    public void testBinIndexForRangeEqual90() {
-        final int binCount = 10;
-        final int minValue = 100;
-        final int maxValue = 100000;
-
-        Histogram.UniformOptions options = new Histogram.UniformOptions(binCount, minValue,
-                maxValue);
-
-        // logging underflow sample
-        assertEquals(0, options.getBinForSample(minValue - 1));
-
-        // logging overflow sample
-        assertEquals(binCount + 1, options.getBinForSample(maxValue));
-        assertEquals(binCount + 1, options.getBinForSample(maxValue + 1));
-
-        // logging min edge sample
-        assertEquals(1, options.getBinForSample(minValue));
-
-        // logging max edge sample
-        assertEquals(binCount, options.getBinForSample(maxValue - 1));
-
-        // logging single valid sample per bin
-        final int binSize = (maxValue - minValue) / binCount;
-
-        for (int i = 0; i < binCount; i++) {
-            assertEquals(i + 1, options.getBinForSample(minValue + binSize * i));
-        }
-    }
-}
diff --git a/data/etc/preinstalled-packages-platform-overlays.xml b/data/etc/preinstalled-packages-platform-overlays.xml
index 9959433..cea535e 100644
--- a/data/etc/preinstalled-packages-platform-overlays.xml
+++ b/data/etc/preinstalled-packages-platform-overlays.xml
@@ -56,6 +56,10 @@
     <install-in-user-type package="com.android.internal.systemui.navbar.transparent">
         <install-in user-type="FULL" />
     </install-in-user-type>
+    <install-in-user-type package="com.android.role.notes.enabled">
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
     <install-in-user-type package="com.android.theme.color.amethyst">
         <install-in user-type="FULL" />
         <install-in user-type="PROFILE" />
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 0eb4caa..7434cb0 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -175,12 +175,6 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "-1941440781": {
-      "message": "Creating Pending Move-to-back: %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
-      "at": "com\/android\/server\/wm\/Task.java"
-    },
     "-1939861963": {
       "message": "Create root task displayId=%d winMode=%d",
       "level": "VERBOSE",
@@ -631,12 +625,6 @@
       "group": "WM_DEBUG_CONFIGURATION",
       "at": "com\/android\/server\/wm\/ActivityStarter.java"
     },
-    "-1484988952": {
-      "message": "Creating Pending Multiwindow Fullscreen Request: %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
-      "at": "com\/android\/server\/wm\/ActivityClientController.java"
-    },
     "-1483435730": {
       "message": "InsetsSource setWin %s for type %s",
       "level": "DEBUG",
@@ -2605,12 +2593,6 @@
       "group": "WM_DEBUG_ANIM",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
-    "286170861": {
-      "message": "Creating Pending Transition for TaskFragment: %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
-      "at": "com\/android\/server\/wm\/WindowOrganizerController.java"
-    },
     "288485303": {
       "message": "Attempted to set remove mode to a display that does not exist: %d",
       "level": "WARN",
@@ -3193,12 +3175,6 @@
       "group": "WM_DEBUG_APP_TRANSITIONS",
       "at": "com\/android\/server\/wm\/AppTransitionController.java"
     },
-    "800698875": {
-      "message": "SyncGroup %d: Started when there is other active SyncGroup",
-      "level": "WARN",
-      "group": "WM_DEBUG_SYNC_ENGINE",
-      "at": "com\/android\/server\/wm\/BLASTSyncEngine.java"
-    },
     "801521566": {
       "message": "Content Recording: Attempting to mirror %d from %d but no DisplayContent associated. Changing to mirror default display.",
       "level": "WARN",
@@ -3259,12 +3235,6 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/RootWindowContainer.java"
     },
-    "898260097": {
-      "message": "Creating Pending Pip-Enter: %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
-      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
-    },
     "898863925": {
       "message": "Attempted to add QS dialog window with unknown token %s.  Aborting.",
       "level": "WARN",
@@ -4009,12 +3979,6 @@
       "group": "WM_DEBUG_CONTENT_RECORDING",
       "at": "com\/android\/server\/wm\/ContentRecorder.java"
     },
-    "1667162379": {
-      "message": "Creating Pending Transition: %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
-      "at": "com\/android\/server\/wm\/WindowOrganizerController.java"
-    },
     "1670933628": {
       "message": " Setting allReady override",
       "level": "VERBOSE",
@@ -4075,6 +4039,12 @@
       "group": "WM_DEBUG_SYNC_ENGINE",
       "at": "com\/android\/server\/wm\/BLASTSyncEngine.java"
     },
+    "1735199721": {
+      "message": "Queueing transition: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
+      "at": "com\/android\/server\/wm\/TransitionController.java"
+    },
     "1739298851": {
       "message": "removeWindowToken: Attempted to remove token: %s for non-exiting displayId=%d",
       "level": "WARN",
diff --git a/data/keyboards/GoogleTV-Remote.idc b/data/keyboards/GoogleTV-Remote.idc
new file mode 100644
index 0000000..14fb4e2
--- /dev/null
+++ b/data/keyboards/GoogleTV-Remote.idc
@@ -0,0 +1,25 @@
+# Copyright 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.
+
+#
+# Input Device Configuration file for Google Reference Remote Control Unit (RCU).
+#
+#
+
+# Basic Parameters
+# Depending on the FLASH configurations, RCUs may have PID 0006 instead
+# of 0001.
+keyboard.layout = Vendor_0957_Product_0001
+keyboard.doNotWakeByDefault = 1
+audio.mic = 1
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 25b074d..2307d60 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -401,8 +401,9 @@
     /**
      * This is called by methods that want to throw an exception if the bitmap
      * has already been recycled.
+     * @hide
      */
-    private void checkRecycled(String errorMessage) {
+    void checkRecycled(String errorMessage) {
         if (mRecycled) {
             throw new IllegalStateException(errorMessage);
         }
diff --git a/graphics/java/android/graphics/BitmapShader.java b/graphics/java/android/graphics/BitmapShader.java
index 2f6dd46..5c06577 100644
--- a/graphics/java/android/graphics/BitmapShader.java
+++ b/graphics/java/android/graphics/BitmapShader.java
@@ -120,6 +120,7 @@
         if (bitmap == null) {
             throw new IllegalArgumentException("Bitmap must be non-null");
         }
+        bitmap.checkRecycled("Cannot create BitmapShader for recycled bitmap");
         mBitmap = bitmap;
         mTileX = tileX;
         mTileY = tileY;
@@ -188,6 +189,8 @@
     /** @hide */
     @Override
     protected long createNativeInstance(long nativeMatrix, boolean filterFromPaint) {
+        mBitmap.checkRecycled("BitmapShader's bitmap has been recycled");
+
         boolean enableLinearFilter = mFilterMode == FILTER_MODE_LINEAR;
         if (mFilterMode == FILTER_MODE_DEFAULT) {
             mFilterFromPaint = filterFromPaint;
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 0b29973..56c3068 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -2083,32 +2083,29 @@
             }
 
             sIsP010SupportedForAV1Initialized = true;
-
-            if (hasHardwareDecoder("video/av01")) {
-                sIsP010SupportedForAV1 = true;
-                return true;
-            }
-
-            sIsP010SupportedForAV1 = Build.VERSION.DEVICE_INITIAL_SDK_INT
-                    >= Build.VERSION_CODES.S;
-            return sIsP010SupportedForAV1;
+            return sIsP010SupportedForAV1 = isP010SupportedforMime("video/av01");
         }
     }
 
     /**
-     * Checks if the device has hardware decoder for the target mime type.
+     * Checks if the device supports decoding 10-bit for the given mime type.
      */
-    private static boolean hasHardwareDecoder(String mime) {
-        final MediaCodecList sMCL = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-        for (MediaCodecInfo info : sMCL.getCodecInfos()) {
-            if (info.isEncoder() == false && info.isHardwareAccelerated()) {
-                try {
-                     if (info.getCapabilitiesForType(mime) != null) {
-                         return true;
-                     }
-                } catch (IllegalArgumentException e) {
-                     // mime is not supported
-                     return false;
+    private static boolean isP010SupportedforMime(String mime) {
+        MediaCodecList codecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
+        for (MediaCodecInfo mediaCodecInfo : codecList.getCodecInfos()) {
+            if (mediaCodecInfo.isEncoder()) {
+                continue;
+            }
+            for (String mediaType : mediaCodecInfo.getSupportedTypes()) {
+                if (mediaType.equalsIgnoreCase(mime)) {
+                    MediaCodecInfo.CodecCapabilities codecCapabilities =
+                        mediaCodecInfo.getCapabilitiesForType(mediaType);
+                    for (int i = 0; i < codecCapabilities.colorFormats.length; ++i) {
+                        if (codecCapabilities.colorFormats[i]
+                            == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUVP010) {
+                            return true;
+                        }
+                    }
                 }
             }
         }
diff --git a/libs/WindowManager/Jetpack/Android.bp b/libs/WindowManager/Jetpack/Android.bp
index a5b192c..abe8f85 100644
--- a/libs/WindowManager/Jetpack/Android.bp
+++ b/libs/WindowManager/Jetpack/Android.bp
@@ -55,20 +55,6 @@
 }
 
 // Extensions
-// NOTE: This module is still under active development and must not
-// be used in production. Use 'androidx.window.sidecar' instead.
-android_library_import {
-    name: "window-extensions",
-    aars: ["window-extensions-release.aar"],
-    sdk_version: "current",
-}
-
-android_library_import {
-    name: "window-extensions-core",
-    aars: ["window-extensions-core-release.aar"],
-    sdk_version: "current",
-}
-
 java_library {
     name: "androidx.window.extensions",
     srcs: [
@@ -77,8 +63,8 @@
         "src/androidx/window/common/**/*.java",
     ],
     static_libs: [
-        "window-extensions",
-        "window-extensions-core",
+        "androidx.window.extensions_extensions-nodeps",
+        "androidx.window.extensions.core_core-nodeps",
     ],
     installable: true,
     sdk_version: "core_platform",
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index 8386131..a7a6b3c 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -122,16 +122,6 @@
         addWindowLayoutInfoListener(activity, extConsumer);
     }
 
-    @Override
-    public void addWindowLayoutInfoListener(@NonNull @UiContext Context context,
-            @NonNull java.util.function.Consumer<WindowLayoutInfo> consumer) {
-        final Consumer<WindowLayoutInfo> extConsumer = consumer::accept;
-        synchronized (mLock) {
-            mJavaToExtConsumers.put(consumer, extConsumer);
-        }
-        addWindowLayoutInfoListener(context, extConsumer);
-    }
-
     /**
      * Similar to {@link #addWindowLayoutInfoListener(Activity, java.util.function.Consumer)}, but
      * takes a UI Context as a parameter.
diff --git a/libs/WindowManager/Jetpack/window-extensions-core-release.aar b/libs/WindowManager/Jetpack/window-extensions-core-release.aar
deleted file mode 100644
index 96ff840..0000000
--- a/libs/WindowManager/Jetpack/window-extensions-core-release.aar
+++ /dev/null
Binary files differ
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
deleted file mode 100644
index c3b6916..0000000
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ /dev/null
Binary files differ
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index 521a65c..bfbddbb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -22,6 +22,7 @@
 import static java.util.Objects.requireNonNull;
 
 import android.content.Context;
+import android.graphics.Rect;
 import android.os.IBinder;
 import android.util.ArrayMap;
 import android.view.SurfaceControl;
@@ -35,6 +36,9 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.util.TransitionUtil;
+
+import java.util.List;
 
 /**
  * Responsible for handling ActivityEmbedding related transitions.
@@ -86,12 +90,13 @@
             @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         boolean containsEmbeddingSplit = false;
-        for (TransitionInfo.Change change : info.getChanges()) {
+        boolean containsNonEmbeddedChange = false;
+        final List<TransitionInfo.Change> changes = info.getChanges();
+        for (int i = changes.size() - 1; i >= 0; i--) {
+            final TransitionInfo.Change change = changes.get(i);
             if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
-                // Only animate the transition if all changes are in a Task with ActivityEmbedding.
-                return false;
-            }
-            if (!containsEmbeddingSplit && !change.hasFlags(FLAG_FILLS_TASK)) {
+                containsNonEmbeddedChange = true;
+            } else if (!change.hasFlags(FLAG_FILLS_TASK)) {
                 // Whether the Task contains any ActivityEmbedding split before or after the
                 // transition.
                 containsEmbeddingSplit = true;
@@ -103,6 +108,9 @@
             // such as the device is in a folded state.
             return false;
         }
+        if (containsNonEmbeddedChange && !handleNonEmbeddedChanges(changes)) {
+            return false;
+        }
 
         // Start ActivityEmbedding animation.
         mTransitionCallbacks.put(transition, finishCallback);
@@ -110,6 +118,37 @@
         return true;
     }
 
+    private boolean handleNonEmbeddedChanges(List<TransitionInfo.Change> changes) {
+        final Rect nonClosingEmbeddedArea = new Rect();
+        for (int i = changes.size() - 1; i >= 0; i--) {
+            final TransitionInfo.Change change = changes.get(i);
+            if (!TransitionUtil.isClosingType(change.getMode())) {
+                if (change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
+                    nonClosingEmbeddedArea.union(change.getEndAbsBounds());
+                    continue;
+                }
+                // Not able to handle non-embedded container if it is not closing.
+                return false;
+            }
+        }
+        for (int i = changes.size() - 1; i >= 0; i--) {
+            final TransitionInfo.Change change = changes.get(i);
+            if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)
+                    && !nonClosingEmbeddedArea.contains(change.getEndAbsBounds())) {
+                // Unknown to animate containers outside the area of embedded activities.
+                return false;
+            }
+        }
+        // Drop the non-embedded closing change because it is occluded by embedded activities.
+        for (int i = changes.size() - 1; i >= 0; i--) {
+            final TransitionInfo.Change change = changes.get(i);
+            if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
+                changes.remove(i);
+            }
+        }
+        return true;
+    }
+
     @Nullable
     @Override
     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
index e84a78f..133fd87 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
@@ -33,12 +33,19 @@
      *
      * @param touchX the X touch position of the {@link MotionEvent}.
      * @param touchY the Y touch position of the {@link MotionEvent}.
+     * @param velocityX the X velocity computed from the {@link MotionEvent}.
+     * @param velocityY the Y velocity computed from the {@link MotionEvent}.
      * @param keyAction the original {@link KeyEvent#getAction()} when the event was dispatched to
      *               the process. This is forwarded separately because the input pipeline may mutate
      *               the {#event} action state later.
      * @param swipeEdge the edge from which the swipe begins.
      */
-    void onBackMotion(float touchX, float touchY, int keyAction,
+    void onBackMotion(
+            float touchX,
+            float touchY,
+            float velocityX,
+            float velocityY,
+            int keyAction,
             @BackEvent.SwipeEdge int swipeEdge);
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 210c9aa..47d3a5c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -256,8 +256,20 @@
     private class BackAnimationImpl implements BackAnimation {
         @Override
         public void onBackMotion(
-                float touchX, float touchY, int keyAction, @BackEvent.SwipeEdge int swipeEdge) {
-            mShellExecutor.execute(() -> onMotionEvent(touchX, touchY, keyAction, swipeEdge));
+                float touchX,
+                float touchY,
+                float velocityX,
+                float velocityY,
+                int keyAction,
+                @BackEvent.SwipeEdge int swipeEdge
+        ) {
+            mShellExecutor.execute(() -> onMotionEvent(
+                    /* touchX = */ touchX,
+                    /* touchY = */ touchY,
+                    /* velocityX = */ velocityX,
+                    /* velocityY = */ velocityY,
+                    /* keyAction = */ keyAction,
+                    /* swipeEdge = */ swipeEdge));
         }
 
         @Override
@@ -332,13 +344,18 @@
      * Called when a new motion event needs to be transferred to this
      * {@link BackAnimationController}
      */
-    public void onMotionEvent(float touchX, float touchY, int keyAction,
+    public void onMotionEvent(
+            float touchX,
+            float touchY,
+            float velocityX,
+            float velocityY,
+            int keyAction,
             @BackEvent.SwipeEdge int swipeEdge) {
         if (mPostCommitAnimationInProgress) {
             return;
         }
 
-        mTouchTracker.update(touchX, touchY);
+        mTouchTracker.update(touchX, touchY, velocityX, velocityY);
         if (keyAction == MotionEvent.ACTION_DOWN) {
             if (!mBackGestureStarted) {
                 mShouldStartOnNextMoveEvent = true;
@@ -561,6 +578,9 @@
         }
         if (runner.isWaitingAnimation()) {
             ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Gesture released, but animation didn't ready.");
+            // Supposed it is in post commit animation state, and start the timeout to watch
+            // if the animation is ready.
+            mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION);
             return;
         } else if (runner.isAnimationCancelled()) {
             invokeOrCancelBack();
@@ -577,6 +597,8 @@
         if (mPostCommitAnimationInProgress) {
             return;
         }
+
+        mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable);
         ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startPostCommitAnimation()");
         mPostCommitAnimationInProgress = true;
         mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION);
@@ -595,9 +617,6 @@
      */
     @VisibleForTesting
     void onBackAnimationFinished() {
-        if (!mPostCommitAnimationInProgress) {
-            return;
-        }
         // Stop timeout runner.
         mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable);
         mPostCommitAnimationInProgress = false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
index 695ef4e..904574b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
@@ -42,11 +42,13 @@
      */
     private float mInitTouchX;
     private float mInitTouchY;
+    private float mLatestVelocityX;
+    private float mLatestVelocityY;
     private float mStartThresholdX;
     private int mSwipeEdge;
     private boolean mCancelled;
 
-    void update(float touchX, float touchY) {
+    void update(float touchX, float touchY, float velocityX, float velocityY) {
         /**
          * If back was previously cancelled but the user has started swiping in the forward
          * direction again, restart back.
@@ -58,6 +60,8 @@
         }
         mLatestTouchX = touchX;
         mLatestTouchY = touchY;
+        mLatestVelocityX = velocityX;
+        mLatestVelocityY = velocityY;
     }
 
     void setTriggerBack(boolean triggerBack) {
@@ -84,7 +88,14 @@
     }
 
     BackMotionEvent createStartEvent(RemoteAnimationTarget target) {
-        return new BackMotionEvent(mInitTouchX, mInitTouchY, 0, mSwipeEdge, target);
+        return new BackMotionEvent(
+                /* touchX = */ mInitTouchX,
+                /* touchY = */ mInitTouchY,
+                /* progress = */ 0,
+                /* velocityX = */ 0,
+                /* velocityY = */ 0,
+                /* swipeEdge = */ mSwipeEdge,
+                /* departingAnimationTarget = */ target);
     }
 
     BackMotionEvent createProgressEvent() {
@@ -111,7 +122,14 @@
     }
 
     BackMotionEvent createProgressEvent(float progress) {
-        return new BackMotionEvent(mLatestTouchX, mLatestTouchY, progress, mSwipeEdge, null);
+        return new BackMotionEvent(
+                /* touchX = */ mLatestTouchX,
+                /* touchY = */ mLatestTouchY,
+                /* progress = */ progress,
+                /* velocityX = */ mLatestVelocityX,
+                /* velocityY = */ mLatestVelocityY,
+                /* swipeEdge = */ mSwipeEdge,
+                /* departingAnimationTarget = */ null);
     }
 
     public void setProgressThreshold(float progressThreshold) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 670b24c..0400963 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -25,6 +25,7 @@
 import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
 import android.app.WindowConfiguration.WindowingMode
 import android.content.Context
+import android.graphics.Point
 import android.graphics.Rect
 import android.os.IBinder
 import android.os.SystemProperties
@@ -193,6 +194,21 @@
         }
     }
 
+
+    /**
+     * Move a task to fullscreen after being dragged from fullscreen and released back into
+     * status bar area
+     */
+    fun cancelMoveToFreeform(task: RunningTaskInfo, startPosition: Point) {
+        val wct = WindowContainerTransaction()
+        addMoveToFullscreenChanges(wct, task.token)
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct, startPosition)
+        } else {
+            shellTaskOrganizer.applyTransaction(wct)
+        }
+    }
+
     fun moveToFullscreenWithAnimation(task: ActivityManager.RunningTaskInfo) {
         val wct = WindowContainerTransaction()
         addMoveToFullscreenChanges(wct, task.token)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index 3df2340..27eda16 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -17,11 +17,13 @@
 package com.android.wm.shell.desktopmode;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.app.ActivityManager;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.view.SurfaceControl;
@@ -55,6 +57,7 @@
     public static final int FREEFORM_ANIMATION_DURATION = 336;
 
     private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
+    private Point mStartPosition;
 
     public EnterDesktopTaskTransitionHandler(
             Transitions transitions) {
@@ -79,6 +82,17 @@
         mPendingTransitionTokens.add(token);
     }
 
+    /**
+     * Starts Transition of type TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
+     * @param wct WindowContainerTransaction for transition
+     * @param startPosition Position of task when transition is triggered
+     */
+    public void startCancelMoveToDesktopMode(@NonNull WindowContainerTransaction wct,
+            Point startPosition) {
+        mStartPosition = startPosition;
+        startTransition(Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE, wct);
+    }
+
     @Override
     public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startT,
@@ -173,6 +187,37 @@
             return true;
         }
 
+        if (type == Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
+                && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+                && mStartPosition != null) {
+            // This Transition animates a task to fullscreen after being dragged from the status
+            // bar and then released back into the status bar area
+            final SurfaceControl sc = change.getLeash();
+            startT.setWindowCrop(sc, null);
+            startT.apply();
+
+            final ValueAnimator animator = new ValueAnimator();
+            animator.setFloatValues(DRAG_FREEFORM_SCALE, 1f);
+            animator.setDuration(FREEFORM_ANIMATION_DURATION);
+            final SurfaceControl.Transaction t = mTransactionSupplier.get();
+            animator.addUpdateListener(animation -> {
+                final float scale = animation.getAnimatedFraction();
+                t.setPosition(sc, mStartPosition.x * (1 - scale),
+                        mStartPosition.y * (1 - scale));
+                t.setScale(sc, scale, scale);
+                t.apply();
+            });
+            animator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mTransitions.getMainExecutor().execute(
+                            () -> finishCallback.onTransitionFinished(null, null));
+                }
+            });
+            animator.start();
+            return true;
+        }
+
         return false;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index f70df83..1d7e649 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -30,14 +30,17 @@
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.graphics.Rect;
+import android.os.SystemClock;
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.window.TaskSnapshot;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.transition.Transitions;
 
 import java.lang.annotation.Retention;
@@ -61,6 +64,14 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface AnimationType {}
 
+    /**
+     * The alpha type is set for swiping to home. But the swiped task may not enter PiP. And if
+     * another task enters PiP by non-swipe ways, e.g. call API in foreground or switch to 3-button
+     * navigation, then the alpha type is unexpected. So use a timeout to avoid applying wrong
+     * animation style to an unrelated task.
+     */
+    private static final int ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS = 800;
+
     public static final int TRANSITION_DIRECTION_NONE = 0;
     public static final int TRANSITION_DIRECTION_SAME = 1;
     public static final int TRANSITION_DIRECTION_TO_PIP = 2;
@@ -109,6 +120,9 @@
             });
 
     private PipTransitionAnimator mCurrentAnimator;
+    @AnimationType
+    private int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
+    private long mLastOneShotAlphaAnimationTime;
 
     public PipAnimationController(PipSurfaceTransactionHelper helper) {
         mSurfaceTransactionHelper = helper;
@@ -222,6 +236,37 @@
     }
 
     /**
+     * Sets the preferred enter animation type for one time. This is typically used to set the
+     * animation type to {@link PipAnimationController#ANIM_TYPE_ALPHA}.
+     * <p>
+     * For example, gesture navigation would first fade out the PiP activity, and the transition
+     * should be responsible to animate in (such as fade in) the PiP.
+     */
+    public void setOneShotEnterAnimationType(@AnimationType int animationType) {
+        mOneShotAnimationType = animationType;
+        if (animationType == ANIM_TYPE_ALPHA) {
+            mLastOneShotAlphaAnimationTime = SystemClock.uptimeMillis();
+        }
+    }
+
+    /** Returns the preferred animation type and consumes the one-shot type if needed. */
+    @AnimationType
+    public int takeOneShotEnterAnimationType() {
+        final int type = mOneShotAnimationType;
+        if (type == ANIM_TYPE_ALPHA) {
+            // Restore to default type.
+            mOneShotAnimationType = ANIM_TYPE_BOUNDS;
+            if (SystemClock.uptimeMillis() - mLastOneShotAlphaAnimationTime
+                    > ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS) {
+                ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                        "Alpha animation is expired. Use bounds animation.");
+                return ANIM_TYPE_BOUNDS;
+            }
+        }
+        return type;
+    }
+
+    /**
      * Additional callback interface for PiP animation
      */
     public static class PipAnimationCallback {
@@ -255,7 +300,7 @@
          * @return true if handled by the handler, false otherwise.
          */
         public boolean handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx,
-                Rect destinationBounds) {
+                Rect destinationBounds, float alpha) {
             return false;
         }
     }
@@ -356,9 +401,10 @@
         }
 
         boolean handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx,
-                Rect destinationBounds) {
+                Rect destinationBounds, float alpha) {
             if (mPipTransactionHandler != null) {
-                return mPipTransactionHandler.handlePipTransaction(leash, tx, destinationBounds);
+                return mPipTransactionHandler.handlePipTransaction(
+                        leash, tx, destinationBounds, alpha);
             }
             return false;
         }
@@ -503,7 +549,9 @@
                     getSurfaceTransactionHelper().alpha(tx, leash, alpha)
                             .round(tx, leash, shouldApplyCornerRadius())
                             .shadow(tx, leash, shouldApplyShadowRadius());
-                    tx.apply();
+                    if (!handlePipTransaction(leash, tx, destinationBounds, alpha)) {
+                        tx.apply();
+                    }
                 }
 
                 @Override
@@ -618,7 +666,7 @@
                                     .shadow(tx, leash, shouldApplyShadowRadius());
                         }
                     }
-                    if (!handlePipTransaction(leash, tx, bounds)) {
+                    if (!handlePipTransaction(leash, tx, bounds, /* alpha= */ 1f)) {
                         tx.apply();
                     }
                 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
index 0006244..0775f52 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
@@ -45,6 +45,13 @@
     String MENU_WINDOW_TITLE = "PipMenuView";
 
     /**
+     * Used with
+     * {@link PipMenuController#movePipMenu(SurfaceControl, SurfaceControl.Transaction, Rect,
+     * float)} to indicate that we don't want to affect the alpha value of the menu surfaces.
+     */
+    float ALPHA_NO_CHANGE = -1f;
+
+    /**
      * Called when
      * {@link PipTaskOrganizer#onTaskAppeared(RunningTaskInfo, SurfaceControl)}
      * is called.
@@ -85,8 +92,8 @@
      * need to synchronize the movements on the same frame as PiP.
      */
     default void movePipMenu(@Nullable SurfaceControl pipLeash,
-            @Nullable SurfaceControl.Transaction t,
-            Rect destinationBounds) {}
+            @Nullable SurfaceControl.Transaction t, Rect destinationBounds, float alpha) {
+    }
 
     /**
      * Update the PiP menu with the given bounds for re-layout purposes.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index a0bd064..23706a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -62,7 +62,6 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.util.Log;
 import android.view.Choreographer;
@@ -111,12 +110,6 @@
         DisplayController.OnDisplaysChangedListener, ShellTaskOrganizer.FocusListener {
     private static final String TAG = PipTaskOrganizer.class.getSimpleName();
     private static final boolean DEBUG = false;
-    /**
-     * The alpha type is set for swiping to home. But the swiped task may not enter PiP. And if
-     * another task enters PiP by non-swipe ways, e.g. call API in foreground or switch to 3-button
-     * navigation, then the alpha type is unexpected.
-     */
-    private static final int ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS = 1000;
 
     /**
      * The fixed start delay in ms when fading out the content overlay from bounds animation.
@@ -256,7 +249,7 @@
         }, null);
     }
 
-    private boolean shouldSyncPipTransactionWithMenu() {
+    protected boolean shouldSyncPipTransactionWithMenu() {
         return mPipMenuController.isMenuVisible();
     }
 
@@ -284,9 +277,9 @@
             new PipAnimationController.PipTransactionHandler() {
                 @Override
                 public boolean handlePipTransaction(SurfaceControl leash,
-                        SurfaceControl.Transaction tx, Rect destinationBounds) {
+                        SurfaceControl.Transaction tx, Rect destinationBounds, float alpha) {
                     if (shouldSyncPipTransactionWithMenu()) {
-                        mPipMenuController.movePipMenu(leash, tx, destinationBounds);
+                        mPipMenuController.movePipMenu(leash, tx, destinationBounds, alpha);
                         return true;
                     }
                     return false;
@@ -301,8 +294,6 @@
     private WindowContainerToken mToken;
     private SurfaceControl mLeash;
     protected PipTransitionState mPipTransitionState;
-    private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
-    private long mLastOneShotAlphaAnimationTime;
     private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
             mSurfaceControlTransactionFactory;
     protected PictureInPictureParams mPictureInPictureParams;
@@ -390,6 +381,10 @@
         return mPipTransitionController;
     }
 
+    PipAnimationController.PipTransactionHandler getPipTransactionHandler() {
+        return mPipTransactionHandler;
+    }
+
     public Rect getCurrentOrAnimatingBounds() {
         PipAnimationController.PipTransitionAnimator animator =
                 mPipAnimationController.getCurrentAnimator();
@@ -422,18 +417,6 @@
     }
 
     /**
-     * Sets the preferred animation type for one time.
-     * This is typically used to set the animation type to
-     * {@link PipAnimationController#ANIM_TYPE_ALPHA}.
-     */
-    public void setOneShotAnimationType(@PipAnimationController.AnimationType int animationType) {
-        mOneShotAnimationType = animationType;
-        if (animationType == ANIM_TYPE_ALPHA) {
-            mLastOneShotAlphaAnimationTime = SystemClock.uptimeMillis();
-        }
-    }
-
-    /**
      * Override if the PiP should always use a fade-in animation during PiP entry.
      *
      * @return true if the mOneShotAnimationType should always be
@@ -733,26 +716,17 @@
             return;
         }
 
-        if (mOneShotAnimationType == ANIM_TYPE_ALPHA
-                && SystemClock.uptimeMillis() - mLastOneShotAlphaAnimationTime
-                > ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS) {
-            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                    "%s: Alpha animation is expired. Use bounds animation.", TAG);
-            mOneShotAnimationType = ANIM_TYPE_BOUNDS;
-        }
-
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             // For Shell transition, we will animate the window in PipTransition#startAnimation
             // instead of #onTaskAppeared.
             return;
         }
 
-        if (shouldAlwaysFadeIn()) {
-            mOneShotAnimationType = ANIM_TYPE_ALPHA;
-        }
-
+        final int animationType = shouldAlwaysFadeIn()
+                ? ANIM_TYPE_ALPHA
+                : mPipAnimationController.takeOneShotEnterAnimationType();
         if (mWaitForFixedRotation) {
-            onTaskAppearedWithFixedRotation();
+            onTaskAppearedWithFixedRotation(animationType);
             return;
         }
 
@@ -763,7 +737,7 @@
         Objects.requireNonNull(destinationBounds, "Missing destination bounds");
         final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
 
-        if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
+        if (animationType == ANIM_TYPE_BOUNDS) {
             if (!shouldAttachMenuEarly()) {
                 mPipMenuController.attach(mLeash);
             }
@@ -773,16 +747,15 @@
                     sourceHintRect, TRANSITION_DIRECTION_TO_PIP, mEnterAnimationDuration,
                     null /* updateBoundsCallback */);
             mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP);
-        } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+        } else if (animationType == ANIM_TYPE_ALPHA) {
             enterPipWithAlphaAnimation(destinationBounds, mEnterAnimationDuration);
-            mOneShotAnimationType = ANIM_TYPE_BOUNDS;
         } else {
-            throw new RuntimeException("Unrecognized animation type: " + mOneShotAnimationType);
+            throw new RuntimeException("Unrecognized animation type: " + animationType);
         }
     }
 
-    private void onTaskAppearedWithFixedRotation() {
-        if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+    private void onTaskAppearedWithFixedRotation(int animationType) {
+        if (animationType == ANIM_TYPE_ALPHA) {
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                     "%s: Defer entering PiP alpha animation, fixed rotation is ongoing", TAG);
             // If deferred, hside the surface till fixed rotation is completed.
@@ -791,7 +764,6 @@
             tx.setAlpha(mLeash, 0f);
             tx.show(mLeash);
             tx.apply();
-            mOneShotAnimationType = ANIM_TYPE_BOUNDS;
             return;
         }
         final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
@@ -1417,7 +1389,7 @@
                 .scale(tx, mLeash, startBounds, toBounds, degrees)
                 .round(tx, mLeash, startBounds, toBounds);
         if (shouldSyncPipTransactionWithMenu()) {
-            mPipMenuController.movePipMenu(mLeash, tx, toBounds);
+            mPipMenuController.movePipMenu(mLeash, tx, toBounds, PipMenuController.ALPHA_NO_CHANGE);
         } else {
             tx.apply();
         }
@@ -1583,7 +1555,8 @@
         if (!isInPip()) {
             return;
         }
-        mPipMenuController.movePipMenu(null, null, destinationBounds);
+        mPipMenuController.movePipMenu(null, null, destinationBounds,
+                PipMenuController.ALPHA_NO_CHANGE);
         mPipMenuController.updateMenuBounds(destinationBounds);
     }
 
@@ -1895,7 +1868,6 @@
                 + " binder=" + (mToken != null ? mToken.asBinder() : null));
         pw.println(innerPrefix + "mLeash=" + mLeash);
         pw.println(innerPrefix + "mState=" + mPipTransitionState.getTransitionState());
-        pw.println(innerPrefix + "mOneShotAnimationType=" + mOneShotAnimationType);
         pw.println(innerPrefix + "mPictureInPictureParams=" + mPictureInPictureParams);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 4a76a50..c5e9229 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -87,7 +87,7 @@
     private final int mEnterExitAnimationDuration;
     private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
     private final Optional<SplitScreenController> mSplitScreenOptional;
-    private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
+    private @PipAnimationController.AnimationType int mEnterAnimationType = ANIM_TYPE_BOUNDS;
     private Transitions.TransitionFinishCallback mFinishCallback;
     private SurfaceControl.Transaction mFinishTransaction;
     private final Rect mExitDestinationBounds = new Rect();
@@ -133,20 +133,6 @@
     }
 
     @Override
-    public void setIsFullAnimation(boolean isFullAnimation) {
-        setOneShotAnimationType(isFullAnimation ? ANIM_TYPE_BOUNDS : ANIM_TYPE_ALPHA);
-    }
-
-    /**
-     * Sets the preferred animation type for one time.
-     * This is typically used to set the animation type to
-     * {@link PipAnimationController#ANIM_TYPE_ALPHA}.
-     */
-    private void setOneShotAnimationType(@PipAnimationController.AnimationType int animationType) {
-        mOneShotAnimationType = animationType;
-    }
-
-    @Override
     public void startExitTransition(int type, WindowContainerTransaction out,
             @Nullable Rect destinationBounds) {
         if (destinationBounds != null) {
@@ -288,7 +274,10 @@
         if (!requestHasPipEnter(request)) {
             throw new IllegalStateException("Called PiP augmentRequest when request has no PiP");
         }
-        if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+        mEnterAnimationType = mPipOrganizer.shouldAlwaysFadeIn()
+                ? ANIM_TYPE_ALPHA
+                : mPipAnimationController.takeOneShotEnterAnimationType();
+        if (mEnterAnimationType == ANIM_TYPE_ALPHA) {
             mRequestedEnterTransition = transition;
             mRequestedEnterTask = request.getTriggerTask().token;
             outWCT.setActivityWindowingMode(request.getTriggerTask().token,
@@ -308,7 +297,7 @@
     @Override
     public boolean handleRotateDisplay(int startRotation, int endRotation,
             WindowContainerTransaction wct) {
-        if (mRequestedEnterTransition != null && mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+        if (mRequestedEnterTransition != null && mEnterAnimationType == ANIM_TYPE_ALPHA) {
             // A fade-in was requested but not-yet started. In this case, just recalculate the
             // initial state under the new rotation.
             int rotationDelta = deltaRotation(startRotation, endRotation);
@@ -760,7 +749,6 @@
         if (taskInfo.pictureInPictureParams != null
                 && taskInfo.pictureInPictureParams.isAutoEnterEnabled()
                 && mPipTransitionState.getInSwipePipToHomeTransition()) {
-            mOneShotAnimationType = ANIM_TYPE_BOUNDS;
             final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mSwipePipToHomeOverlay;
             startTransaction.setMatrix(leash, Matrix.IDENTITY_MATRIX, new float[9])
                     .setPosition(leash, destinationBounds.left, destinationBounds.top)
@@ -796,17 +784,16 @@
             startTransaction.setMatrix(leash, tmpTransform, new float[9]);
         }
 
-        if (mPipOrganizer.shouldAlwaysFadeIn()) {
-            mOneShotAnimationType = ANIM_TYPE_ALPHA;
-        }
-
-        if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+        final int enterAnimationType = mEnterAnimationType;
+        if (enterAnimationType == ANIM_TYPE_ALPHA) {
+            // Restore to default type.
+            mEnterAnimationType = ANIM_TYPE_BOUNDS;
             startTransaction.setAlpha(leash, 0f);
         }
         startTransaction.apply();
 
         PipAnimationController.PipTransitionAnimator animator;
-        if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
+        if (enterAnimationType == ANIM_TYPE_BOUNDS) {
             animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds,
                     currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
                     0 /* startingAngle */, rotationDelta);
@@ -829,15 +816,14 @@
                     animator.setColorContentOverlay(mContext);
                 }
             }
-        } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+        } else if (enterAnimationType == ANIM_TYPE_ALPHA) {
             animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds,
                     0f, 1f);
-            mOneShotAnimationType = ANIM_TYPE_BOUNDS;
         } else {
-            throw new RuntimeException("Unrecognized animation type: "
-                    + mOneShotAnimationType);
+            throw new RuntimeException("Unrecognized animation type: " + enterAnimationType);
         }
         animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
+                .setPipTransactionHandler(mPipOrganizer.getPipTransactionHandler())
                 .setPipAnimationCallback(mPipAnimationCallback)
                 .setDuration(mEnterExitAnimationDuration);
         if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) {
@@ -897,7 +883,7 @@
                         .setWindowCrop(leash, endBounds.width(), endBounds.height());
             }
         }
-        mSplitScreenOptional.get().finishEnterSplitScreen(startTransaction);
+        mSplitScreenOptional.get().finishEnterSplitScreen(finishTransaction);
         startTransaction.apply();
 
         mPipOrganizer.onExitPipFinished(taskInfo);
@@ -964,7 +950,8 @@
     }
 
     private void finishResizeForMenu(Rect destinationBounds) {
-        mPipMenuController.movePipMenu(null, null, destinationBounds);
+        mPipMenuController.movePipMenu(null, null, destinationBounds,
+                PipMenuController.ALPHA_NO_CHANGE);
         mPipMenuController.updateMenuBounds(destinationBounds);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index f51e247..7979ce7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -105,15 +105,6 @@
     }
 
     /**
-     * Called to inform the transition that the animation should start with the assumption that
-     * PiP is not animating from its original bounds, but rather a continuation of another
-     * animation. For example, gesture navigation would first fade out the PiP activity, and the
-     * transition should be responsible to animate in (such as fade in) the PiP.
-     */
-    public void setIsFullAnimation(boolean isFullAnimation) {
-    }
-
-    /**
      * Called when the Shell wants to start an exit Pip transition/animation.
      */
     public void startExitTransition(int type, WindowContainerTransaction out,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index 94e593b..e7a1395 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -298,7 +298,8 @@
         }
 
         // Sync the menu bounds before showing it in case it is out of sync.
-        movePipMenu(null /* pipLeash */, null /* transaction */, stackBounds);
+        movePipMenu(null /* pipLeash */, null /* transaction */, stackBounds,
+                PipMenuController.ALPHA_NO_CHANGE);
         updateMenuBounds(stackBounds);
 
         mPipMenuView.showMenu(menuState, stackBounds, allowMenuTimeout, willResizeMenu, withDelay,
@@ -311,7 +312,7 @@
     @Override
     public void movePipMenu(@Nullable SurfaceControl pipLeash,
             @Nullable SurfaceControl.Transaction t,
-            Rect destinationBounds) {
+            Rect destinationBounds, float alpha) {
         if (destinationBounds.isEmpty()) {
             return;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 463ad77..b0bb14b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -968,12 +968,6 @@
         mPipBoundsState.setShelfVisibility(visible, shelfHeight);
     }
 
-    private void setPinnedStackAnimationType(int animationType) {
-        mPipTaskOrganizer.setOneShotAnimationType(animationType);
-        mPipTransitionController.setIsFullAnimation(
-                animationType == PipAnimationController.ANIM_TYPE_BOUNDS);
-    }
-
     @VisibleForTesting
     void setPinnedStackAnimationListener(PipAnimationListener callback) {
         mPinnedStackAnimationRecentsCallback = callback;
@@ -1337,7 +1331,8 @@
         @Override
         public void setPipAnimationTypeToAlpha() {
             executeRemoteCallWithTaskPermission(mController, "setPipAnimationTypeToAlpha",
-                    (controller) -> controller.setPinnedStackAnimationType(ANIM_TYPE_ALPHA));
+                    (controller) -> controller.mPipAnimationController.setOneShotEnterAnimationType(
+                            ANIM_TYPE_ALPHA));
         }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index b18e21c..b2a189b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -30,6 +30,7 @@
 import android.view.SurfaceControl;
 import android.view.View;
 import android.view.ViewRootImpl;
+import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 import android.window.SurfaceSyncGroup;
 
@@ -202,8 +203,10 @@
     }
 
     private void addPipMenuViewToSystemWindows(View v, String title) {
-        mSystemWindows.addView(v, getPipMenuLayoutParams(mContext, title, 0 /* width */,
-                0 /* height */), 0 /* displayId */, SHELL_ROOT_LAYER_PIP);
+        final WindowManager.LayoutParams layoutParams =
+                getPipMenuLayoutParams(mContext, title, 0 /* width */, 0 /* height */);
+        layoutParams.alpha = 0f;
+        mSystemWindows.addView(v, layoutParams, 0 /* displayId */, SHELL_ROOT_LAYER_PIP);
     }
 
     void onPipTransitionFinished(boolean enterTransition) {
@@ -309,9 +312,9 @@
 
     @Override
     public void movePipMenu(SurfaceControl pipLeash, SurfaceControl.Transaction pipTx,
-            Rect pipBounds) {
+            Rect pipBounds, float alpha) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: movePipMenu: %s", TAG, pipBounds.toShortString());
+                "%s: movePipMenu: %s, alpha %s", TAG, pipBounds.toShortString(), alpha);
 
         if (pipBounds.isEmpty()) {
             if (pipTx == null) {
@@ -333,6 +336,11 @@
         pipTx.setPosition(frontSurface, menuDestBounds.left, menuDestBounds.top);
         pipTx.setPosition(backSurface, menuDestBounds.left, menuDestBounds.top);
 
+        if (alpha != ALPHA_NO_CHANGE) {
+            pipTx.setAlpha(frontSurface, alpha);
+            pipTx.setAlpha(backSurface, alpha);
+        }
+
         // Synchronize drawing the content in the front and back surfaces together with the pip
         // transaction and the position change for the front and back surfaces
         final SurfaceSyncGroup syncGroup = new SurfaceSyncGroup("TvPip");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
index 0940490..4819f66 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
@@ -98,4 +98,11 @@
     protected boolean shouldAlwaysFadeIn() {
         return true;
     }
+
+    @Override
+    protected boolean shouldSyncPipTransactionWithMenu() {
+        // We always have a menu visible and want to sync the pip transaction with the menu, even
+        // when the menu alpha is 0 (e.g. when a fade-in animation starts).
+        return true;
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index 81e118a..f819bee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -129,9 +129,10 @@
     /**
      * Start a pair of intents in one transition.
      */
-    oneway void startIntents(in PendingIntent pendingIntent1, in Bundle options1,
-            in PendingIntent pendingIntent2, in Bundle options2, int splitPosition,
-            float splitRatio, in RemoteTransition remoteTransition, in InstanceId instanceId) = 19;
+    oneway void startIntents(in PendingIntent pendingIntent1, in ShortcutInfo shortcutInfo1,
+            in Bundle options1, in PendingIntent pendingIntent2, in ShortcutInfo shortcutInfo2,
+            in Bundle options2, int splitPosition, float splitRatio,
+            in RemoteTransition remoteTransition, in InstanceId instanceId) = 19;
 
     /**
      * Blocking call that notifies and gets additional split-screen targets when entering
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 7d5ab84..498f95c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -541,6 +541,34 @@
                 instanceId);
     }
 
+    void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1,
+            int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
+            float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+        if (options1 == null) options1 = new Bundle();
+        final ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
+        final String packageName1 = shortcutInfo.getPackage();
+        // NOTE: This doesn't correctly pull out packageName2 if taskId is referring to a task in
+        //       recents that hasn't launched and is not being organized
+        final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
+        if (samePackage(packageName1, packageName2)) {
+            if (supportMultiInstancesSplit(packageName1)) {
+                activityOptions.setApplyMultipleTaskFlagForShortcut(true);
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+            } else {
+                if (mRecentTasksOptional.isPresent()) {
+                    mRecentTasksOptional.get().removeSplitPair(taskId);
+                }
+                taskId = INVALID_TASK_ID;
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                        "Cancel entering split as not supporting multi-instances");
+                Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
+                        Toast.LENGTH_SHORT).show();
+            }
+        }
+        mStageCoordinator.startShortcutAndTask(shortcutInfo, options1, taskId, options2,
+                splitPosition, splitRatio, remoteTransition, instanceId);
+    }
+
     /**
      * See {@link #startIntent(PendingIntent, Intent, int, Bundle)}
      * @param instanceId to be used by {@link SplitscreenEventLogger}
@@ -580,6 +608,8 @@
             float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
         Intent fillInIntent = null;
         final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent);
+        // NOTE: This doesn't correctly pull out packageName2 if taskId is referring to a task in
+        //       recents that hasn't launched and is not being organized
         final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
         if (samePackage(packageName1, packageName2)) {
             if (supportMultiInstancesSplit(packageName1)) {
@@ -587,6 +617,10 @@
                 fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
             } else {
+                if (mRecentTasksOptional.isPresent()) {
+                    mRecentTasksOptional.get().removeSplitPair(taskId);
+                }
+                taskId = INVALID_TASK_ID;
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                         "Cancel entering split as not supporting multi-instances");
                 Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
@@ -626,6 +660,35 @@
                 splitPosition, splitRatio, adapter, instanceId);
     }
 
+    private void startIntents(PendingIntent pendingIntent1,
+            @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
+            PendingIntent pendingIntent2, @Nullable ShortcutInfo shortcutInfo2,
+            @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
+            @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+        Intent fillInIntent1 = null;
+        Intent fillInIntent2 = null;
+        final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1);
+        final String packageName2 = SplitScreenUtils.getPackageName(pendingIntent2);
+        if (samePackage(packageName1, packageName2)) {
+            if (supportMultiInstancesSplit(packageName1)) {
+                fillInIntent1 = new Intent();
+                fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+                fillInIntent2 = new Intent();
+                fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+            } else {
+                pendingIntent2 = null;
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                        "Cancel entering split as not supporting multi-instances");
+                Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
+                        Toast.LENGTH_SHORT).show();
+            }
+        }
+        mStageCoordinator.startIntents(pendingIntent1, fillInIntent1, shortcutInfo1, options1,
+                pendingIntent2, fillInIntent2, shortcutInfo2, options2, splitPosition, splitRatio,
+                remoteTransition, instanceId);
+    }
+
     @Override
     public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent,
             @SplitPosition int position, @Nullable Bundle options) {
@@ -1046,9 +1109,8 @@
                 float splitRatio, @Nullable RemoteTransition remoteTransition,
                 InstanceId instanceId) {
             executeRemoteCallWithTaskPermission(mController, "startShortcutAndTask",
-                    (controller) -> controller.mStageCoordinator.startShortcutAndTask(shortcutInfo,
-                            options1, taskId, options2, splitPosition, splitRatio, remoteTransition,
-                            instanceId));
+                    (controller) -> controller.startShortcutAndTask(shortcutInfo, options1, taskId,
+                            options2, splitPosition, splitRatio, remoteTransition, instanceId));
         }
 
         @Override
@@ -1066,11 +1128,17 @@
         }
 
         @Override
-        public void startIntents(PendingIntent pendingIntent1, @Nullable Bundle options1,
-                PendingIntent pendingIntent2, @Nullable Bundle options2,
+        public void startIntents(PendingIntent pendingIntent1, @Nullable ShortcutInfo shortcutInfo1,
+                @Nullable Bundle options1, PendingIntent pendingIntent2,
+                @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2,
                 @SplitPosition int splitPosition, float splitRatio,
                 @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
-            // TODO(b/259368992): To be implemented.
+            executeRemoteCallWithTaskPermission(mController, "startIntents",
+                    (controller) ->
+                            controller.startIntents(pendingIntent1, shortcutInfo1,
+                                    options1, pendingIntent2, shortcutInfo2, options2,
+                                    splitPosition, splitRatio, remoteTransition, instanceId)
+            );
         }
 
         @Override
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 22800ad..2c08cd4 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
@@ -22,6 +22,7 @@
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 
+import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
 import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString;
@@ -155,8 +156,10 @@
             }
             boolean isRootOrSplitSideRoot = change.getParent() == null
                     || topRoot.equals(change.getParent());
-            // For enter or exit, we only want to animate the side roots but not the top-root.
-            if (!isRootOrSplitSideRoot || topRoot.equals(change.getContainer())) {
+            boolean isDivider = change.getFlags() == FLAG_IS_DIVIDER_BAR;
+            // For enter or exit, we only want to animate side roots and the divider but not the
+            // top-root.
+            if (!isRootOrSplitSideRoot || topRoot.equals(change.getContainer()) || isDivider) {
                 continue;
             }
 
@@ -165,6 +168,10 @@
                 t.setPosition(leash, change.getEndAbsBounds().left, change.getEndAbsBounds().top);
                 t.setWindowCrop(leash, change.getEndAbsBounds().width(),
                         change.getEndAbsBounds().height());
+            } else if (isDivider) {
+                t.setPosition(leash, change.getEndAbsBounds().left, change.getEndAbsBounds().top);
+                t.setLayer(leash, Integer.MAX_VALUE);
+                t.show(leash);
             }
             boolean isOpening = isOpeningTransition(info);
             if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
@@ -282,6 +289,12 @@
         return null;
     }
 
+    void startFullscreenTransition(WindowContainerTransaction wct,
+            @Nullable RemoteTransition handler) {
+        mTransitions.startTransition(TRANSIT_OPEN, wct,
+                new OneShotRemoteHandler(mTransitions.getMainExecutor(), handler));
+    }
+
 
     /** Starts a transition to enter split with a remote transition animator. */
     IBinder startEnterTransition(
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 dd91a37..e4f2724 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
@@ -612,6 +612,19 @@
             @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
             @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
+        if (taskId2 == INVALID_TASK_ID) {
+            if (mMainStage.containsTask(taskId1) || mSideStage.containsTask(taskId1)) {
+                prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
+            }
+            if (mRecentTasks.isPresent()) {
+                mRecentTasks.get().removeSplitPair(taskId1);
+            }
+            options1 = options1 != null ? options1 : new Bundle();
+            wct.startTask(taskId1, options1);
+            mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
+            return;
+        }
+
         prepareEvictChildTasksIfSplitActive(wct);
         setSideStagePosition(splitPosition, wct);
         options1 = options1 != null ? options1 : new Bundle();
@@ -627,6 +640,13 @@
             @SplitPosition int splitPosition, float splitRatio,
             @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
+        if (taskId == INVALID_TASK_ID) {
+            options1 = options1 != null ? options1 : new Bundle();
+            wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
+            mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
+            return;
+        }
+
         prepareEvictChildTasksIfSplitActive(wct);
         setSideStagePosition(splitPosition, wct);
         options1 = options1 != null ? options1 : new Bundle();
@@ -641,6 +661,13 @@
             int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
             float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
+        if (taskId == INVALID_TASK_ID) {
+            options1 = options1 != null ? options1 : new Bundle();
+            wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1);
+            mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
+            return;
+        }
+
         prepareEvictChildTasksIfSplitActive(wct);
         setSideStagePosition(splitPosition, wct);
         options1 = options1 != null ? options1 : new Bundle();
@@ -682,6 +709,57 @@
         setEnterInstanceId(instanceId);
     }
 
+    void startIntents(PendingIntent pendingIntent1, Intent fillInIntent1,
+            @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
+            PendingIntent pendingIntent2, Intent fillInIntent2,
+            @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2,
+            @SplitPosition int splitPosition, float splitRatio,
+            @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        if (pendingIntent2 == null) {
+            options1 = options1 != null ? options1 : new Bundle();
+            if (shortcutInfo1 != null) {
+                wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1);
+            } else {
+                wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
+            }
+            mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
+            return;
+        }
+
+        if (!mMainStage.isActive()) {
+            // Build a request WCT that will launch both apps such that task 0 is on the main stage
+            // while task 1 is on the side stage.
+            mMainStage.activate(wct, false /* reparent */);
+        }
+
+        prepareEvictChildTasksIfSplitActive(wct);
+        mSplitLayout.setDivideRatio(splitRatio);
+        updateWindowBounds(mSplitLayout, wct);
+        wct.reorder(mRootTaskInfo.token, true);
+        setRootForceTranslucent(false, wct);
+
+        setSideStagePosition(splitPosition, wct);
+        options1 = options1 != null ? options1 : new Bundle();
+        addActivityOptions(options1, mSideStage);
+        if (shortcutInfo1 != null) {
+            wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1);
+        } else {
+            wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
+        }
+        options2 = options2 != null ? options2 : new Bundle();
+        addActivityOptions(options2, mMainStage);
+        if (shortcutInfo2 != null) {
+            wct.startShortcut(mContext.getPackageName(), shortcutInfo2, options2);
+        } else {
+            wct.sendPendingIntent(pendingIntent2, fillInIntent2, options2);
+        }
+
+        mSplitTransitions.startEnterTransition(
+                TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this, null, null);
+        setEnterInstanceId(instanceId);
+    }
+
     /** Starts a pair of tasks using legacy transition. */
     void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1,
             int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition,
@@ -690,6 +768,10 @@
         if (options1 == null) options1 = new Bundle();
         if (taskId2 == INVALID_TASK_ID) {
             // Launching a solo task.
+            // Exit split first if this task under split roots.
+            if (mMainStage.containsTask(taskId1) || mSideStage.containsTask(taskId1)) {
+                exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
+            }
             ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
             activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
             options1 = activityOptions.toBundle();
@@ -891,10 +973,6 @@
             mSyncQueue.queue(wrapAsSplitRemoteAnimation(adapter), WindowManager.TRANSIT_OPEN, wct);
         }
 
-        mSyncQueue.runInSync(t -> {
-            setDividerVisibility(true, t);
-        });
-
         setEnterInstanceId(instanceId);
     }
 
@@ -933,6 +1011,7 @@
             @Override
             public void onAnimationCancelled(boolean isKeyguardOccluded) {
                 onRemoteAnimationFinishedOrCancelled(evictWct);
+                setDividerVisibility(true, null);
                 try {
                     adapter.getRunner().onAnimationCancelled(isKeyguardOccluded);
                 } catch (RemoteException e) {
@@ -973,6 +1052,7 @@
                             t.setPosition(apps[i].leash, 0, 0);
                         }
                     }
+                    setDividerVisibility(true, t);
                     t.apply();
 
                     IRemoteAnimationFinishedCallback wrapCallback =
@@ -1463,6 +1543,10 @@
     void finishEnterSplitScreen(SurfaceControl.Transaction t) {
         mSplitLayout.init();
         setDividerVisibility(true, t);
+        // Ensure divider surface are re-parented back into the hierarchy at the end of the
+        // transition. See Transition#buildFinishTransaction for more detail.
+        t.reparent(mSplitLayout.getDividerLeash(), mRootTaskLeash);
+
         updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
         t.show(mRootTaskLeash);
         setSplitsVisible(true);
@@ -1772,6 +1856,8 @@
         setDividerVisibility(mainStageVisible, null);
     }
 
+    // Set divider visibility flag and try to apply it, the param transaction is used to apply.
+    // See applyDividerVisibility for more detail.
     private void setDividerVisibility(boolean visible, @Nullable SurfaceControl.Transaction t) {
         if (visible == mDividerVisible) {
             return;
@@ -1798,14 +1884,13 @@
             return;
         }
 
-        if (t != null) {
-            applyDividerVisibility(t);
-        } else {
-            mSyncQueue.runInSync(transaction -> applyDividerVisibility(transaction));
-        }
+        applyDividerVisibility(t);
     }
 
-    private void applyDividerVisibility(SurfaceControl.Transaction t) {
+    // Apply divider visibility by current visibility flag. If param transaction is non-null, it
+    // will apply by that transaction, if it is null and visible, it will run a fade-in animation,
+    // otherwise hide immediately.
+    private void applyDividerVisibility(@Nullable SurfaceControl.Transaction t) {
         final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
         if (dividerLeash == null) {
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
@@ -1822,7 +1907,12 @@
             mDividerFadeInAnimator.cancel();
         }
 
-        if (mDividerVisible) {
+        mSplitLayout.getRefDividerBounds(mTempRect1);
+        if (t != null) {
+            t.setVisibility(dividerLeash, mDividerVisible);
+            t.setLayer(dividerLeash, Integer.MAX_VALUE);
+            t.setPosition(dividerLeash, mTempRect1.left, mTempRect1.top);
+        } else if (mDividerVisible) {
             final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
             mDividerFadeInAnimator = ValueAnimator.ofFloat(0f, 1f);
             mDividerFadeInAnimator.addUpdateListener(animation -> {
@@ -1862,7 +1952,10 @@
 
             mDividerFadeInAnimator.start();
         } else {
-            t.hide(dividerLeash);
+            final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+            transaction.hide(dividerLeash);
+            transaction.apply();
+            mTransactionPool.release(transaction);
         }
     }
 
@@ -2321,9 +2414,14 @@
                 }
             }
             if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
-                // TODO(shell-transitions): Implement a fallback behavior for now.
-                throw new IllegalStateException("Somehow removed the last task in a stage"
-                        + " outside of a proper transition");
+                Log.e(TAG, "Somehow removed the last task in a stage outside of a proper "
+                        + "transition.");
+                final WindowContainerTransaction wct = new WindowContainerTransaction();
+                final int dismissTop = mMainStage.getChildCount() == 0
+                        ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+                prepareExitSplitScreen(dismissTop, wct);
+                mSplitTransitions.startDismissTransition(wct, this, dismissTop,
+                        EXIT_REASON_UNKNOWN);
                 // This can happen in some pathological cases. For example:
                 // 1. main has 2 tasks [Task A (Single-task), Task B], side has one task [Task C]
                 // 2. Task B closes itself and starts Task A in LAUNCH_ADJACENT at the same time
@@ -2446,7 +2544,7 @@
         }
 
         finishEnterSplitScreen(finishT);
-        addDividerBarToTransition(info, finishT, true /* show */);
+        addDividerBarToTransition(info, true /* show */);
         return true;
     }
 
@@ -2589,7 +2687,7 @@
         if (dismissTransition.mDismissTop == STAGE_TYPE_UNDEFINED) {
             // TODO: Have a proper remote for this. Until then, though, reset state and use the
             //       normal animation stuff (which falls back to the normal launcher remote).
-            t.hide(mSplitLayout.getDividerLeash());
+            setDividerVisibility(false, t);
             mSplitLayout.release(t);
             mSplitTransitions.mPendingDismiss = null;
             return false;
@@ -2607,7 +2705,7 @@
                     });
         }
 
-        addDividerBarToTransition(info, finishT, false /* show */);
+        addDividerBarToTransition(info, false /* show */);
         return true;
     }
 
@@ -2648,11 +2746,11 @@
         logExit(EXIT_REASON_UNKNOWN);
     }
 
-    private void addDividerBarToTransition(@NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction finishT, boolean show) {
+    private void addDividerBarToTransition(@NonNull TransitionInfo info, boolean show) {
         final SurfaceControl leash = mSplitLayout.getDividerLeash();
         final TransitionInfo.Change barChange = new TransitionInfo.Change(null /* token */, leash);
         mSplitLayout.getRefDividerBounds(mTempRect1);
+        barChange.setParent(mRootTaskInfo.token);
         barChange.setStartAbsBounds(mTempRect1);
         barChange.setEndAbsBounds(mTempRect1);
         barChange.setMode(show ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK);
@@ -2660,15 +2758,6 @@
         // Technically this should be order-0, but this is running after layer assignment
         // and it's a special case, so just add to end.
         info.addChange(barChange);
-
-        if (show) {
-            finishT.setLayer(leash, Integer.MAX_VALUE);
-            finishT.setPosition(leash, mTempRect1.left, mTempRect1.top);
-            finishT.show(leash);
-            // Ensure divider surface are re-parented back into the hierarchy at the end of the
-            // transition. See Transition#buildFinishTransaction for more detail.
-            finishT.reparent(leash, mRootTaskLeash);
-        }
     }
 
     RemoteAnimationTarget getDividerBarLegacyTarget() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 3dd10a0..6e9ecda 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -327,6 +327,7 @@
 
         @ColorInt int backgroundColorForTransition = 0;
         final int wallpaperTransit = getWallpaperTransitType(info);
+        boolean isDisplayRotationAnimationStarted = false;
         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
             final TransitionInfo.Change change = info.getChanges().get(i);
             if (change.hasAllFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY
@@ -350,6 +351,7 @@
                     if (!(isSeamlessDisplayChange || anim == ROTATION_ANIMATION_JUMPCUT)) {
                         startRotationAnimation(startTransaction, change, info, anim, animations,
                                 onAnimFinish);
+                        isDisplayRotationAnimationStarted = true;
                         continue;
                     }
                 } else {
@@ -405,6 +407,14 @@
                 }
             }
 
+            // Hide the invisible surface directly without animating it if there is a display
+            // rotation animation playing.
+            if (isDisplayRotationAnimationStarted && TransitionUtil.isClosingType(
+                    change.getMode())) {
+                startTransaction.hide(change.getLeash());
+                continue;
+            }
+
             // The back gesture has animated this change before transition happen, so here we don't
             // play the animation again.
             if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index fa4de16..bdb7d44 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -143,6 +143,10 @@
     /** Transition type to fullscreen from desktop mode. */
     public static final int TRANSIT_EXIT_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 12;
 
+    /** Transition type to animate back to fullscreen when drag to freeform is cancelled. */
+    public static final int TRANSIT_CANCEL_ENTERING_DESKTOP_MODE =
+            WindowManager.TRANSIT_FIRST_CUSTOM + 13;
+
     private final WindowOrganizer mOrganizer;
     private final Context mContext;
     private final ShellExecutor mMainExecutor;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 060dc4e..dfde7e6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -110,19 +110,11 @@
         final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
         final WindowContainerTransaction wct = new WindowContainerTransaction();
 
-        final int outsetLeftId = R.dimen.freeform_resize_handle;
-        final int outsetTopId = R.dimen.freeform_resize_handle;
-        final int outsetRightId = R.dimen.freeform_resize_handle;
-        final int outsetBottomId = R.dimen.freeform_resize_handle;
-
         mRelayoutParams.reset();
         mRelayoutParams.mRunningTaskInfo = taskInfo;
         mRelayoutParams.mLayoutResId = R.layout.caption_window_decor;
         mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
         mRelayoutParams.mShadowRadiusId = shadowRadiusID;
-        if (isDragResizeable) {
-            mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId);
-        }
 
         relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
         // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index afc573e..5226eee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -34,6 +34,7 @@
 import android.app.ActivityTaskManager;
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.input.InputManager;
 import android.os.Handler;
@@ -557,8 +558,10 @@
                         mDragToDesktopAnimationStarted = false;
                         return;
                     } else if (mDragToDesktopAnimationStarted) {
-                        mDesktopTasksController.ifPresent(c ->
-                                c.moveToFullscreen(relevantDecor.mTaskInfo));
+                        Point startPosition = new Point((int) ev.getX(), (int) ev.getY());
+                        mDesktopTasksController.ifPresent(
+                                c -> c.cancelMoveToFreeform(relevantDecor.mTaskInfo,
+                                        startPosition));
                         mDragToDesktopAnimationStarted = false;
                         return;
                     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index f9c0e60..67e99d7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -42,6 +42,7 @@
 import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.TextView;
+import android.window.SurfaceSyncGroup;
 import android.window.WindowContainerTransaction;
 
 import com.android.launcher3.icons.IconProvider;
@@ -208,11 +209,6 @@
         final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
         final WindowContainerTransaction wct = new WindowContainerTransaction();
 
-        final int outsetLeftId = R.dimen.freeform_resize_handle;
-        final int outsetTopId = R.dimen.freeform_resize_handle;
-        final int outsetRightId = R.dimen.freeform_resize_handle;
-        final int outsetBottomId = R.dimen.freeform_resize_handle;
-
         final int windowDecorLayoutId = getDesktopModeWindowDecorLayoutId(
                 taskInfo.getWindowingMode());
         mRelayoutParams.reset();
@@ -220,9 +216,6 @@
         mRelayoutParams.mLayoutResId = windowDecorLayoutId;
         mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
         mRelayoutParams.mShadowRadiusId = shadowRadiusID;
-        if (isDragResizeable) {
-            mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId);
-        }
 
         relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
         // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
@@ -319,51 +312,50 @@
      * Create and display handle menu window
      */
     void createHandleMenu() {
+        final SurfaceSyncGroup ssg = new SurfaceSyncGroup(TAG);
         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
         updateHandleMenuPillPositions();
 
-        createAppInfoPill(t);
+        createAppInfoPill(t, ssg);
 
         // Only show windowing buttons in proto2. Proto1 uses a system-level mode only.
         final boolean shouldShowWindowingPill = DesktopModeStatus.isProto2Enabled();
         if (shouldShowWindowingPill) {
-            createWindowingPill(t);
+            createWindowingPill(t, ssg);
         }
 
-        createMoreActionsPill(t);
+        createMoreActionsPill(t, ssg);
 
-        mSyncQueue.runInSync(transaction -> {
-            transaction.merge(t);
-            t.close();
-        });
+        ssg.addTransaction(t);
+        ssg.markSyncReady();
         setupHandleMenu(shouldShowWindowingPill);
     }
 
-    private void createAppInfoPill(SurfaceControl.Transaction t) {
+    private void createAppInfoPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
         final int x = (int) mHandleMenuAppInfoPillPosition.x;
         final int y = (int) mHandleMenuAppInfoPillPosition.y;
         mHandleMenuAppInfoPill = addWindow(
                 R.layout.desktop_mode_window_decor_handle_menu_app_info_pill,
                 "Menu's app info pill",
-                t, x, y, mMenuWidth, mAppInfoPillHeight, mShadowRadius, mCornerRadius);
+                t, ssg, x, y, mMenuWidth, mAppInfoPillHeight, mShadowRadius, mCornerRadius);
     }
 
-    private void createWindowingPill(SurfaceControl.Transaction t) {
+    private void createWindowingPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
         final int x = (int) mHandleMenuWindowingPillPosition.x;
         final int y = (int) mHandleMenuWindowingPillPosition.y;
         mHandleMenuWindowingPill = addWindow(
                 R.layout.desktop_mode_window_decor_handle_menu_windowing_pill,
                 "Menu's windowing pill",
-                t, x, y, mMenuWidth, mWindowingPillHeight, mShadowRadius, mCornerRadius);
+                t, ssg, x, y, mMenuWidth, mWindowingPillHeight, mShadowRadius, mCornerRadius);
     }
 
-    private void createMoreActionsPill(SurfaceControl.Transaction t) {
+    private void createMoreActionsPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
         final int x = (int) mHandleMenuMoreActionsPillPosition.x;
         final int y = (int) mHandleMenuMoreActionsPillPosition.y;
         mHandleMenuMoreActionsPill = addWindow(
                 R.layout.desktop_mode_window_decor_handle_menu_more_actions_pill,
                 "Menu's more actions pill",
-                t, x, y, mMenuWidth, mMoreActionsPillHeight, mShadowRadius, mCornerRadius);
+                t, ssg, x, y, mMenuWidth, mMoreActionsPillHeight, mShadowRadius, mCornerRadius);
     }
 
     private void setupHandleMenu(boolean windowingPillShown) {
@@ -424,13 +416,12 @@
         if (mRelayoutParams.mLayoutResId
                 == R.layout.desktop_mode_app_controls_window_decor) {
             // Align the handle menu to the left of the caption.
-            menuX = mRelayoutParams.mCaptionX - mResult.mDecorContainerOffsetX + mMarginMenuStart;
-            menuY = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY + mMarginMenuTop;
+            menuX = mRelayoutParams.mCaptionX + mMarginMenuStart;
+            menuY = mRelayoutParams.mCaptionY + mMarginMenuTop;
         } else {
             // Position the handle menu at the center of the caption.
-            menuX = mRelayoutParams.mCaptionX + (captionWidth / 2) - (mMenuWidth / 2)
-                    - mResult.mDecorContainerOffsetX;
-            menuY = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY + mMarginMenuStart;
+            menuX = mRelayoutParams.mCaptionX + (captionWidth / 2) - (mMenuWidth / 2);
+            menuY = mRelayoutParams.mCaptionY + mMarginMenuStart;
         }
 
         // App Info pill setup.
@@ -497,23 +488,18 @@
 
         final boolean pointInAppInfoPill = pointInView(
                 mHandleMenuAppInfoPill.mWindowViewHost.getView(),
-                inputPoint.x - mHandleMenuAppInfoPillPosition.x - mResult.mDecorContainerOffsetX,
-                inputPoint.y - mHandleMenuAppInfoPillPosition.y
-                        - mResult.mDecorContainerOffsetY);
+                inputPoint.x - mHandleMenuAppInfoPillPosition.x,
+                inputPoint.y - mHandleMenuAppInfoPillPosition.y);
         boolean pointInWindowingPill = false;
         if (mHandleMenuWindowingPill != null) {
             pointInWindowingPill = pointInView(mHandleMenuWindowingPill.mWindowViewHost.getView(),
-                    inputPoint.x - mHandleMenuWindowingPillPosition.x
-                            - mResult.mDecorContainerOffsetX,
-                    inputPoint.y - mHandleMenuWindowingPillPosition.y
-                            - mResult.mDecorContainerOffsetY);
+                    inputPoint.x - mHandleMenuWindowingPillPosition.x,
+                    inputPoint.y - mHandleMenuWindowingPillPosition.y);
         }
         final boolean pointInMoreActionsPill = pointInView(
                 mHandleMenuMoreActionsPill.mWindowViewHost.getView(),
-                inputPoint.x - mHandleMenuMoreActionsPillPosition.x
-                        - mResult.mDecorContainerOffsetX,
-                inputPoint.y - mHandleMenuMoreActionsPillPosition.y
-                        - mResult.mDecorContainerOffsetY);
+                inputPoint.x - mHandleMenuMoreActionsPillPosition.x,
+                inputPoint.y - mHandleMenuMoreActionsPillPosition.y);
         if (!pointInAppInfoPill && !pointInWindowingPill
                 && !pointInMoreActionsPill && !pointInOpenMenuButton) {
             closeHandleMenu();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 8cb575c..d5437c7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -64,8 +64,8 @@
     private final TaskResizeInputEventReceiver mInputEventReceiver;
     private final DragPositioningCallback mCallback;
 
-    private int mWidth;
-    private int mHeight;
+    private int mTaskWidth;
+    private int mTaskHeight;
     private int mResizeHandleThickness;
     private int mCornerSize;
 
@@ -128,78 +128,84 @@
      * This is also used to update the touch regions of this handler every event dispatched here is
      * a potential resize request.
      *
-     * @param width The width of the drag resize handler in pixels, including resize handle
-     *              thickness. That is task width + 2 * resize handle thickness.
-     * @param height The height of the drag resize handler in pixels, including resize handle
-     *               thickness. That is task height + 2 * resize handle thickness.
+     * @param taskWidth The width of the task.
+     * @param taskHeight The height of the task.
      * @param resizeHandleThickness The thickness of the resize handle in pixels.
      * @param cornerSize The size of the resize handle centered in each corner.
      * @param touchSlop The distance in pixels user has to drag with touch for it to register as
      *                  a resize action.
      */
-    void setGeometry(int width, int height, int resizeHandleThickness, int cornerSize,
+    void setGeometry(int taskWidth, int taskHeight, int resizeHandleThickness, int cornerSize,
             int touchSlop) {
-        if (mWidth == width && mHeight == height
+        if (mTaskWidth == taskWidth && mTaskHeight == taskHeight
                 && mResizeHandleThickness == resizeHandleThickness
                 && mCornerSize == cornerSize) {
             return;
         }
 
-        mWidth = width;
-        mHeight = height;
+        mTaskWidth = taskWidth;
+        mTaskHeight = taskHeight;
         mResizeHandleThickness = resizeHandleThickness;
         mCornerSize = cornerSize;
         mDragDetector.setTouchSlop(touchSlop);
 
         Region touchRegion = new Region();
-        final Rect topInputBounds = new Rect(0, 0, mWidth, mResizeHandleThickness);
+        final Rect topInputBounds = new Rect(
+                -mResizeHandleThickness,
+                -mResizeHandleThickness,
+                mTaskWidth + mResizeHandleThickness,
+                0);
         touchRegion.union(topInputBounds);
 
-        final Rect leftInputBounds = new Rect(0, mResizeHandleThickness,
-                mResizeHandleThickness, mHeight - mResizeHandleThickness);
+        final Rect leftInputBounds = new Rect(
+                -mResizeHandleThickness,
+                0,
+                0,
+                mTaskHeight);
         touchRegion.union(leftInputBounds);
 
         final Rect rightInputBounds = new Rect(
-                mWidth - mResizeHandleThickness, mResizeHandleThickness,
-                mWidth, mHeight - mResizeHandleThickness);
+                mTaskWidth,
+                0,
+                mTaskWidth + mResizeHandleThickness,
+                mTaskHeight);
         touchRegion.union(rightInputBounds);
 
-        final Rect bottomInputBounds = new Rect(0, mHeight - mResizeHandleThickness,
-                mWidth, mHeight);
+        final Rect bottomInputBounds = new Rect(
+                -mResizeHandleThickness,
+                mTaskHeight,
+                mTaskWidth + mResizeHandleThickness,
+                mTaskHeight + mResizeHandleThickness);
         touchRegion.union(bottomInputBounds);
 
         // Set up touch areas in each corner.
         int cornerRadius = mCornerSize / 2;
         mLeftTopCornerBounds = new Rect(
-                mResizeHandleThickness - cornerRadius,
-                mResizeHandleThickness - cornerRadius,
-                mResizeHandleThickness + cornerRadius,
-                mResizeHandleThickness + cornerRadius
-        );
+                -cornerRadius,
+                -cornerRadius,
+                cornerRadius,
+                cornerRadius);
         touchRegion.union(mLeftTopCornerBounds);
 
         mRightTopCornerBounds = new Rect(
-                mWidth - mResizeHandleThickness - cornerRadius,
-                mResizeHandleThickness - cornerRadius,
-                mWidth - mResizeHandleThickness + cornerRadius,
-                mResizeHandleThickness + cornerRadius
-        );
+                mTaskWidth - cornerRadius,
+                -cornerRadius,
+                mTaskWidth + cornerRadius,
+                cornerRadius);
         touchRegion.union(mRightTopCornerBounds);
 
         mLeftBottomCornerBounds = new Rect(
-                mResizeHandleThickness - cornerRadius,
-                mHeight - mResizeHandleThickness - cornerRadius,
-                mResizeHandleThickness + cornerRadius,
-                mHeight - mResizeHandleThickness + cornerRadius
-        );
+                -cornerRadius,
+                mTaskHeight - cornerRadius,
+                cornerRadius,
+                mTaskHeight + cornerRadius);
         touchRegion.union(mLeftBottomCornerBounds);
 
         mRightBottomCornerBounds = new Rect(
-                mWidth - mResizeHandleThickness - cornerRadius,
-                mHeight - mResizeHandleThickness - cornerRadius,
-                mWidth - mResizeHandleThickness + cornerRadius,
-                mHeight - mResizeHandleThickness + cornerRadius
-        );
+                mTaskWidth - cornerRadius,
+                mTaskHeight - cornerRadius,
+                mTaskWidth + cornerRadius,
+                mTaskHeight + cornerRadius);
         touchRegion.union(mRightBottomCornerBounds);
 
         try {
@@ -358,16 +364,16 @@
         @TaskPositioner.CtrlType
         private int calculateResizeHandlesCtrlType(float x, float y) {
             int ctrlType = 0;
-            if (x < mResizeHandleThickness) {
+            if (x < 0) {
                 ctrlType |= TaskPositioner.CTRL_TYPE_LEFT;
             }
-            if (x > mWidth - mResizeHandleThickness) {
+            if (x > mTaskWidth) {
                 ctrlType |= TaskPositioner.CTRL_TYPE_RIGHT;
             }
-            if (y < mResizeHandleThickness) {
+            if (y < 0) {
                 ctrlType |= TaskPositioner.CTRL_TYPE_TOP;
             }
-            if (y > mHeight - mResizeHandleThickness) {
+            if (y > mTaskHeight) {
                 ctrlType |= TaskPositioner.CTRL_TYPE_BOTTOM;
             }
             return ctrlType;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 4ebd09f..19a3182 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -34,6 +34,7 @@
 import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.WindowlessWindowManager;
+import android.window.SurfaceSyncGroup;
 import android.window.TaskConstants;
 import android.window.WindowContainerTransaction;
 
@@ -98,7 +99,6 @@
 
     private final Binder mOwner = new Binder();
     private final Rect mCaptionInsetsRect = new Rect();
-    private final Rect mTaskSurfaceCrop = new Rect();
     private final float[] mTmpColor = new float[3];
 
     WindowDecoration(
@@ -193,13 +193,13 @@
             mDecorWindowContext = mContext.createConfigurationContext(taskConfig);
             if (params.mLayoutResId != 0) {
                 outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
-                                .inflate(params.mLayoutResId, null);
+                        .inflate(params.mLayoutResId, null);
             }
         }
 
         if (outResult.mRootView == null) {
             outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
-                            .inflate(params.mLayoutResId , null);
+                    .inflate(params.mLayoutResId, null);
         }
 
         // DecorationContainerSurface
@@ -218,21 +218,14 @@
 
         final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
         final Resources resources = mDecorWindowContext.getResources();
-        outResult.mDecorContainerOffsetX = -loadDimensionPixelSize(resources, params.mOutsetLeftId);
-        outResult.mDecorContainerOffsetY = -loadDimensionPixelSize(resources, params.mOutsetTopId);
-        outResult.mWidth = taskBounds.width()
-                + loadDimensionPixelSize(resources, params.mOutsetRightId)
-                - outResult.mDecorContainerOffsetX;
-        outResult.mHeight = taskBounds.height()
-                + loadDimensionPixelSize(resources, params.mOutsetBottomId)
-                - outResult.mDecorContainerOffsetY;
-        startT.setPosition(
-                        mDecorationContainerSurface,
-                        outResult.mDecorContainerOffsetX, outResult.mDecorContainerOffsetY)
-                .setWindowCrop(mDecorationContainerSurface,
-                        outResult.mWidth, outResult.mHeight)
+        outResult.mWidth = taskBounds.width();
+        outResult.mHeight = taskBounds.height();
+        startT.setWindowCrop(mDecorationContainerSurface, outResult.mWidth, outResult.mHeight)
                 .show(mDecorationContainerSurface);
 
+        // TODO(b/270202228): This surface can be removed. Instead, use
+        //  |mDecorationContainerSurface| to set the background now that it no longer has outsets
+        //  and its crop is set to the task bounds.
         // TaskBackgroundSurface
         if (mTaskBackgroundSurface == null) {
             final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
@@ -250,8 +243,7 @@
         mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f;
         mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
         mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
-        startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(),
-                        taskBounds.height())
+        startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(), taskBounds.height())
                 .setShadowRadius(mTaskBackgroundSurface, shadowRadius)
                 .setColor(mTaskBackgroundSurface, mTmpColor)
                 .show(mTaskBackgroundSurface);
@@ -269,11 +261,7 @@
         final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
         final int captionWidth = taskBounds.width();
 
-        startT.setPosition(
-                        mCaptionContainerSurface,
-                        -outResult.mDecorContainerOffsetX + params.mCaptionX,
-                        -outResult.mDecorContainerOffsetY + params.mCaptionY)
-                .setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
+        startT.setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
                 .show(mCaptionContainerSurface);
 
         if (mCaptionWindowManager == null) {
@@ -314,14 +302,9 @@
 
         // Task surface itself
         Point taskPosition = mTaskInfo.positionInParent;
-        mTaskSurfaceCrop.set(
-                outResult.mDecorContainerOffsetX,
-                outResult.mDecorContainerOffsetY,
-                outResult.mWidth + outResult.mDecorContainerOffsetX,
-                outResult.mHeight + outResult.mDecorContainerOffsetY);
         startT.show(mTaskSurface);
         finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
-                .setCrop(mTaskSurface, mTaskSurfaceCrop);
+                .setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
     }
 
     /**
@@ -400,18 +383,20 @@
     /**
      * Create a window associated with this WindowDecoration.
      * Note that subclass must dispose of this when the task is hidden/closed.
-     * @param layoutId layout to make the window from
-     * @param t the transaction to apply
-     * @param xPos x position of new window
-     * @param yPos y position of new window
-     * @param width width of new window
-     * @param height height of new window
+     *
+     * @param layoutId     layout to make the window from
+     * @param t            the transaction to apply
+     * @param xPos         x position of new window
+     * @param yPos         y position of new window
+     * @param width        width of new window
+     * @param height       height of new window
      * @param shadowRadius radius of the shadow of the new window
      * @param cornerRadius radius of the corners of the new window
      * @return the {@link AdditionalWindow} that was added.
      */
     AdditionalWindow addWindow(int layoutId, String namePrefix, SurfaceControl.Transaction t,
-            int xPos, int yPos, int width, int height, int shadowRadius, int cornerRadius) {
+            SurfaceSyncGroup ssg, int xPos, int yPos, int width, int height, int shadowRadius,
+            int cornerRadius) {
         final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
         SurfaceControl windowSurfaceControl = builder
                 .setName(namePrefix + " of Task=" + mTaskInfo.taskId)
@@ -435,49 +420,27 @@
                 windowSurfaceControl, null /* hostInputToken */);
         SurfaceControlViewHost viewHost = mSurfaceControlViewHostFactory
                 .create(mDecorWindowContext, mDisplay, windowManager);
-        viewHost.setView(v, lp);
+        ssg.add(viewHost.getSurfacePackage(), () -> viewHost.setView(v, lp));
         return new AdditionalWindow(windowSurfaceControl, viewHost,
                 mSurfaceControlTransactionSupplier);
     }
 
-    static class RelayoutParams{
+    static class RelayoutParams {
         RunningTaskInfo mRunningTaskInfo;
         int mLayoutResId;
         int mCaptionHeightId;
         int mCaptionWidthId;
         int mShadowRadiusId;
 
-        int mOutsetTopId;
-        int mOutsetBottomId;
-        int mOutsetLeftId;
-        int mOutsetRightId;
-
         int mCaptionX;
         int mCaptionY;
 
-        void setOutsets(int leftId, int topId, int rightId, int bottomId) {
-            mOutsetLeftId = leftId;
-            mOutsetTopId = topId;
-            mOutsetRightId = rightId;
-            mOutsetBottomId = bottomId;
-        }
-
-        void setCaptionPosition(int left, int top) {
-            mCaptionX = left;
-            mCaptionY = top;
-        }
-
         void reset() {
             mLayoutResId = Resources.ID_NULL;
             mCaptionHeightId = Resources.ID_NULL;
             mCaptionWidthId = Resources.ID_NULL;
             mShadowRadiusId = Resources.ID_NULL;
 
-            mOutsetTopId = Resources.ID_NULL;
-            mOutsetBottomId = Resources.ID_NULL;
-            mOutsetLeftId = Resources.ID_NULL;
-            mOutsetRightId = Resources.ID_NULL;
-
             mCaptionX = 0;
             mCaptionY = 0;
         }
@@ -487,14 +450,10 @@
         int mWidth;
         int mHeight;
         T mRootView;
-        int mDecorContainerOffsetX;
-        int mDecorContainerOffsetY;
 
         void reset() {
             mWidth = 0;
             mHeight = 0;
-            mDecorContainerOffsetX = 0;
-            mDecorContainerOffsetY = 0;
             mRootView = null;
         }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
index 67ca9a1..b6d92814 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
@@ -29,6 +29,7 @@
         <option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" />
         <option name="teardown-command" value="settings delete system show_touches" />
         <option name="teardown-command" value="settings delete system pointer_location" />
+        <option name="teardown-command" value="cmd overlay enable com.android.internal.systemui.navbar.gestural" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true"/>
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
index cbbb291..b8f615a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.activityembedding;
 
+import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
@@ -82,10 +83,13 @@
 
     @Test
     public void testStartAnimation_containsNonActivityEmbeddingChange() {
+        final TransitionInfo.Change nonEmbeddedOpen = createChange(0 /* flags */);
+        final TransitionInfo.Change embeddedOpen = createEmbeddedChange(
+                EMBEDDED_LEFT_BOUNDS, EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS);
+        nonEmbeddedOpen.setMode(TRANSIT_OPEN);
         final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
-                .addChange(createEmbeddedChange(
-                        EMBEDDED_LEFT_BOUNDS, EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS))
-                .addChange(createChange(0 /* flags */))
+                .addChange(embeddedOpen)
+                .addChange(nonEmbeddedOpen)
                 .build();
 
         // No-op because it contains non-embedded change.
@@ -95,6 +99,22 @@
         verifyNoMoreInteractions(mStartTransaction);
         verifyNoMoreInteractions(mFinishTransaction);
         verifyNoMoreInteractions(mFinishCallback);
+
+        final TransitionInfo.Change nonEmbeddedClose = createChange(0 /* flags */);
+        nonEmbeddedClose.setMode(TRANSIT_CLOSE);
+        nonEmbeddedClose.setEndAbsBounds(TASK_BOUNDS);
+        final TransitionInfo.Change embeddedOpen2 = createEmbeddedChange(
+                EMBEDDED_RIGHT_BOUNDS, EMBEDDED_RIGHT_BOUNDS, TASK_BOUNDS);
+        final TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
+                .addChange(embeddedOpen)
+                .addChange(embeddedOpen2)
+                .addChange(nonEmbeddedClose)
+                .build();
+        // Ok to animate because nonEmbeddedClose is occluded by embeddedOpen and embeddedOpen2.
+        assertTrue(mController.startAnimation(mTransition, info2, mStartTransaction,
+                mFinishTransaction, mFinishCallback));
+        // The non-embedded change is dropped to avoid affecting embedded animation.
+        assertFalse(info2.getChanges().contains(nonEmbeddedClose));
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 806bffe..d95c7a4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -166,6 +166,9 @@
         doMotionEvent(MotionEvent.ACTION_DOWN, 0);
         doMotionEvent(MotionEvent.ACTION_MOVE, 0);
         mController.setTriggerBack(true);
+    }
+
+    private void releaseBackGesture() {
         doMotionEvent(MotionEvent.ACTION_UP, 0);
     }
 
@@ -201,6 +204,8 @@
                     .setOnBackNavigationDone(new RemoteCallback(result)));
             triggerBackGesture();
             simulateRemoteAnimationStart(type);
+            mShellExecutor.flushAll();
+            releaseBackGesture();
             simulateRemoteAnimationFinished();
             mShellExecutor.flushAll();
 
@@ -252,6 +257,7 @@
         createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, false);
 
         triggerBackGesture();
+        releaseBackGesture();
 
         verify(mAppCallback, times(1)).onBackInvoked();
 
@@ -269,6 +275,8 @@
 
         triggerBackGesture();
         simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+        releaseBackGesture();
+
         // Check that back invocation is dispatched.
         verify(mAnimatorCallback).onBackInvoked();
         verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
@@ -308,6 +316,9 @@
 
         triggerBackGesture();
         simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+        mShellExecutor.flushAll();
+
+        releaseBackGesture();
 
         // Simulate transition timeout.
         mShellExecutor.flushAll();
@@ -369,6 +380,9 @@
             simulateRemoteAnimationStart(type);
             mShellExecutor.flushAll();
 
+            releaseBackGesture();
+            mShellExecutor.flushAll();
+
             assertTrue("Navigation Done callback not called for "
                     + BackNavigationInfo.typeToString(type), result.mBackNavigationDone);
             assertTrue("TriggerBack should have been true", result.mTriggerBack);
@@ -395,6 +409,8 @@
                 .setOnBackNavigationDone(new RemoteCallback(result)));
         triggerBackGesture();
         mShellExecutor.flushAll();
+        releaseBackGesture();
+        mShellExecutor.flushAll();
 
         assertTrue("Navigation Done callback not called for "
                 + BackNavigationInfo.typeToString(type), result.mBackNavigationDone);
@@ -458,9 +474,12 @@
 
     private void doMotionEvent(int actionDown, int coordinate) {
         mController.onMotionEvent(
-                coordinate, coordinate,
-                actionDown,
-                BackEvent.EDGE_LEFT);
+                /* touchX */ coordinate,
+                /* touchY */ coordinate,
+                /* velocityX = */ 0,
+                /* velocityY = */ 0,
+                /* keyAction */ actionDown,
+                /* swipeEdge */ BackEvent.EDGE_LEFT);
     }
 
     private void simulateRemoteAnimationStart(int type) throws RemoteException {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
index 3608474..874ef80 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
@@ -16,8 +16,6 @@
 
 package com.android.wm.shell.back;
 
-import static android.window.BackEvent.EDGE_LEFT;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
@@ -48,12 +46,21 @@
     private CountDownLatch mTargetProgressCalled = new CountDownLatch(1);
     private Handler mMainThreadHandler;
 
+    private BackMotionEvent backMotionEventFrom(float touchX, float progress) {
+        return new BackMotionEvent(
+                /* touchX = */ touchX,
+                /* touchY = */ 0,
+                /* progress = */ progress,
+                /* velocityX = */ 0,
+                /* velocityY = */ 0,
+                /* swipeEdge = */ BackEvent.EDGE_LEFT,
+                /* departingAnimationTarget = */ null);
+    }
+
     @Before
     public void setUp() throws Exception {
         mMainThreadHandler = new Handler(Looper.getMainLooper());
-        final BackMotionEvent backEvent = new BackMotionEvent(
-                0, 0,
-                0, EDGE_LEFT, null);
+        final BackMotionEvent backEvent = backMotionEventFrom(0, 0);
         mMainThreadHandler.post(
                 () -> {
                     mProgressAnimator = new BackProgressAnimator();
@@ -63,9 +70,7 @@
 
     @Test
     public void testBackProgressed() throws InterruptedException {
-        final BackMotionEvent backEvent = new BackMotionEvent(
-                100, 0,
-                mTargetProgress, EDGE_LEFT, null);
+        final BackMotionEvent backEvent = backMotionEventFrom(100, mTargetProgress);
         mMainThreadHandler.post(
                 () -> mProgressAnimator.onBackProgressed(backEvent));
 
@@ -78,9 +83,7 @@
     @Test
     public void testBackCancelled() throws InterruptedException {
         // Give the animator some progress.
-        final BackMotionEvent backEvent = new BackMotionEvent(
-                100, 0,
-                mTargetProgress, EDGE_LEFT, null);
+        final BackMotionEvent backEvent = backMotionEventFrom(100, mTargetProgress);
         mMainThreadHandler.post(
                 () -> mProgressAnimator.onBackProgressed(backEvent));
         mTargetProgressCalled.await(1, TimeUnit.SECONDS);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
index ba9c159..d62e660 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
@@ -47,43 +47,45 @@
     public void generatesProgress_leftEdge() {
         mTouchTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0, BackEvent.EDGE_LEFT);
         float touchX = 10;
+        float velocityX = 0;
+        float velocityY = 0;
 
         // Pre-commit
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
 
         // Post-commit
         touchX += 100;
         mTouchTracker.setTriggerBack(true);
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
 
         // Cancel
         touchX -= 10;
         mTouchTracker.setTriggerBack(false);
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), 0, 0f);
 
         // Cancel more
         touchX -= 10;
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), 0, 0f);
 
         // Restart
         touchX += 10;
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), 0, 0f);
 
         // Restarted, but pre-commit
         float restartX = touchX;
         touchX += 10;
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), (touchX - restartX) / FAKE_THRESHOLD, 0f);
 
         // Restarted, post-commit
         touchX += 10;
         mTouchTracker.setTriggerBack(true);
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
     }
 
@@ -91,43 +93,45 @@
     public void generatesProgress_rightEdge() {
         mTouchTracker.setGestureStartLocation(INITIAL_X_RIGHT_EDGE, 0, BackEvent.EDGE_RIGHT);
         float touchX = INITIAL_X_RIGHT_EDGE - 10; // Fake right edge
+        float velocityX = 0f;
+        float velocityY = 0f;
 
         // Pre-commit
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
 
         // Post-commit
         touchX -= 100;
         mTouchTracker.setTriggerBack(true);
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
 
         // Cancel
         touchX += 10;
         mTouchTracker.setTriggerBack(false);
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), 0, 0f);
 
         // Cancel more
         touchX += 10;
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), 0, 0f);
 
         // Restart
         touchX -= 10;
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), 0, 0f);
 
         // Restarted, but pre-commit
         float restartX = touchX;
         touchX -= 10;
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), (restartX - touchX) / FAKE_THRESHOLD, 0f);
 
         // Restarted, post-commit
         touchX -= 10;
         mTouchTracker.setTriggerBack(true);
-        mTouchTracker.update(touchX, 0);
+        mTouchTracker.update(touchX, 0, velocityX, velocityY);
         assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 15bb10e..842c699 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -262,7 +262,8 @@
         DisplayLayout layout = new DisplayLayout(info,
                 mContext.getResources(), true, true);
         mPipDisplayLayoutState.setDisplayLayout(layout);
-        mPipTaskOrganizer.setOneShotAnimationType(PipAnimationController.ANIM_TYPE_ALPHA);
+        doReturn(PipAnimationController.ANIM_TYPE_ALPHA).when(mMockPipAnimationController)
+                .takeOneShotEnterAnimationType();
         mPipTaskOrganizer.setSurfaceControlTransactionFactory(
                 MockSurfaceControlHelper::createMockSurfaceControlTransaction);
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index eda6fdc..e6219d1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -113,6 +113,7 @@
 
     private SurfaceSession mSurfaceSession = new SurfaceSession();
     private SurfaceControl mRootLeash;
+    private SurfaceControl mDividerLeash;
     private ActivityManager.RunningTaskInfo mRootTask;
     private StageCoordinator mStageCoordinator;
     private Transitions mTransitions;
@@ -129,12 +130,14 @@
                 mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController,
                 mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool,
                 mMainExecutor, Optional.empty()));
+        mDividerLeash = new SurfaceControl.Builder(mSurfaceSession).setName("fakeDivider").build();
 
         when(mSplitLayout.getBounds1()).thenReturn(mBounds1);
         when(mSplitLayout.getBounds2()).thenReturn(mBounds2);
         when(mSplitLayout.getRootBounds()).thenReturn(mRootBounds);
         when(mSplitLayout.isLandscape()).thenReturn(false);
         when(mSplitLayout.applyTaskChanges(any(), any(), any())).thenReturn(true);
+        when(mSplitLayout.getDividerLeash()).thenReturn(mDividerLeash);
 
         mRootTask = new TestRunningTaskInfoBuilder().build();
         mRootLeash = new SurfaceControl.Builder(mSurfaceSession).setName("test").build();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index dfa3c10..38a519a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -49,6 +49,7 @@
 import android.view.ViewRootImpl;
 import android.view.WindowInsets;
 import android.view.WindowManager.LayoutParams;
+import android.window.SurfaceSyncGroup;
 import android.window.TaskConstants;
 import android.window.WindowContainerTransaction;
 
@@ -100,6 +101,8 @@
     private TestView mMockView;
     @Mock
     private WindowContainerTransaction mMockWindowContainerTransaction;
+    @Mock
+    private SurfaceSyncGroup mMockSurfaceSyncGroup;
 
     private final List<SurfaceControl.Transaction> mMockSurfaceControlTransactions =
             new ArrayList<>();
@@ -159,14 +162,8 @@
                 .setVisible(false)
                 .build();
         taskInfo.isFocused = false;
-        // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
-        // 64px.
+        // Density is 2. Shadow radius is 10px. Caption height is 64px.
         taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
-        mRelayoutParams.setOutsets(
-                R.dimen.test_window_decor_left_outset,
-                R.dimen.test_window_decor_top_outset,
-                R.dimen.test_window_decor_right_outset,
-                R.dimen.test_window_decor_bottom_outset);
 
         final SurfaceControl taskSurface = mock(SurfaceControl.class);
         final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
@@ -213,14 +210,8 @@
                 .setVisible(true)
                 .build();
         taskInfo.isFocused = true;
-        // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
-        // 64px.
+        // Density is 2. Shadow radius is 10px. Caption height is 64px.
         taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
-        mRelayoutParams.setOutsets(
-                R.dimen.test_window_decor_left_outset,
-                R.dimen.test_window_decor_top_outset,
-                R.dimen.test_window_decor_right_outset,
-                R.dimen.test_window_decor_bottom_outset);
         final SurfaceControl taskSurface = mock(SurfaceControl.class);
         final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
 
@@ -229,8 +220,7 @@
         verify(decorContainerSurfaceBuilder).setParent(taskSurface);
         verify(decorContainerSurfaceBuilder).setContainerLayer();
         verify(mMockSurfaceControlStartT).setTrustedOverlay(decorContainerSurface, true);
-        verify(mMockSurfaceControlStartT).setPosition(decorContainerSurface, -20, -40);
-        verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 380, 220);
+        verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 300, 100);
 
         verify(taskBackgroundSurfaceBuilder).setParent(taskSurface);
         verify(taskBackgroundSurfaceBuilder).setEffectLayer();
@@ -244,7 +234,6 @@
 
         verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
         verify(captionContainerSurfaceBuilder).setContainerLayer();
-        verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40);
         verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
         verify(mMockSurfaceControlStartT).show(captionContainerSurface);
 
@@ -268,12 +257,12 @@
         verify(mMockSurfaceControlFinishT)
                 .setPosition(taskSurface, TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y);
         verify(mMockSurfaceControlFinishT)
-                .setCrop(taskSurface, new Rect(-20, -40, 360, 180));
+                .setWindowCrop(taskSurface, 300, 100);
         verify(mMockSurfaceControlStartT)
                 .show(taskSurface);
 
-        assertEquals(380, mRelayoutResult.mWidth);
-        assertEquals(220, mRelayoutResult.mHeight);
+        assertEquals(300, mRelayoutResult.mWidth);
+        assertEquals(100, mRelayoutResult.mHeight);
     }
 
     @Test
@@ -309,14 +298,8 @@
                 .setVisible(true)
                 .build();
         taskInfo.isFocused = true;
-        // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
-        // 64px.
+        // Density is 2. Shadow radius is 10px. Caption height is 64px.
         taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
-        mRelayoutParams.setOutsets(
-                R.dimen.test_window_decor_left_outset,
-                R.dimen.test_window_decor_top_outset,
-                R.dimen.test_window_decor_right_outset,
-                R.dimen.test_window_decor_bottom_outset);
 
         final SurfaceControl taskSurface = mock(SurfaceControl.class);
         final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
@@ -419,11 +402,6 @@
                 .build();
         taskInfo.isFocused = true;
         taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
-        mRelayoutParams.setOutsets(
-                R.dimen.test_window_decor_left_outset,
-                R.dimen.test_window_decor_top_outset,
-                R.dimen.test_window_decor_right_outset,
-                R.dimen.test_window_decor_bottom_outset);
         final SurfaceControl taskSurface = mock(SurfaceControl.class);
         final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
         windowDecor.relayout(taskInfo);
@@ -438,7 +416,7 @@
         verify(additionalWindowSurfaceBuilder).setContainerLayer();
         verify(additionalWindowSurfaceBuilder).setParent(decorContainerSurface);
         verify(additionalWindowSurfaceBuilder).build();
-        verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 20, 40);
+        verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 0, 0);
         final int width = WindowDecoration.loadDimensionPixelSize(
                 mContext.getResources(), mCaptionMenuWidthId);
         final int height = WindowDecoration.loadDimensionPixelSize(
@@ -496,11 +474,6 @@
                 .build();
         taskInfo.isFocused = true;
         taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
-        mRelayoutParams.setOutsets(
-                R.dimen.test_window_decor_left_outset,
-                R.dimen.test_window_decor_top_outset,
-                R.dimen.test_window_decor_right_outset,
-                R.dimen.test_window_decor_bottom_outset);
         final SurfaceControl taskSurface = mock(SurfaceControl.class);
         final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
 
@@ -508,7 +481,6 @@
 
         verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
         verify(captionContainerSurfaceBuilder).setContainerLayer();
-        verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40);
         // Width of the captionContainerSurface should match the width of TASK_BOUNDS
         verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
         verify(mMockSurfaceControlStartT).show(captionContainerSurface);
@@ -584,9 +556,7 @@
             String name = "Test Window";
             WindowDecoration.AdditionalWindow additionalWindow =
                     addWindow(R.layout.desktop_mode_window_decor_handle_menu_app_info_pill, name,
-                            mMockSurfaceControlAddWindowT,
-                            x - mRelayoutResult.mDecorContainerOffsetX,
-                            y - mRelayoutResult.mDecorContainerOffsetY,
+                            mMockSurfaceControlAddWindowT, mMockSurfaceSyncGroup, x, y,
                             width, height, shadowRadius, cornerRadius);
             return additionalWindow;
         }
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 2a8cb42..c4d3f5c 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -53,6 +53,8 @@
 }
 
 MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() {
+    bool wasSurfaceless = mEglManager.isCurrent(EGL_NO_SURFACE);
+
     // In case the surface was destroyed (e.g. a previous trimMemory call) we
     // need to recreate it here.
     if (mHardwareBuffer) {
@@ -65,6 +67,37 @@
     if (!mEglManager.makeCurrent(mEglSurface, &error)) {
         return MakeCurrentResult::AlreadyCurrent;
     }
+
+    // Make sure read/draw buffer state of default framebuffer is GL_BACK. Vendor implementations
+    // disagree on the draw/read buffer state if the default framebuffer transitions from a surface
+    // to EGL_NO_SURFACE and vice-versa. There was a related discussion within Khronos on this topic.
+    // See https://cvs.khronos.org/bugzilla/show_bug.cgi?id=13534.
+    // The discussion was not resolved with a clear consensus
+    if (error == 0 && wasSurfaceless && mEglSurface != EGL_NO_SURFACE) {
+        GLint curReadFB = 0;
+        GLint curDrawFB = 0;
+        glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &curReadFB);
+        glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &curDrawFB);
+
+        GLint buffer = GL_NONE;
+        glBindFramebuffer(GL_FRAMEBUFFER, 0);
+        glGetIntegerv(GL_DRAW_BUFFER0, &buffer);
+        if (buffer == GL_NONE) {
+            const GLenum drawBuffer = GL_BACK;
+            glDrawBuffers(1, &drawBuffer);
+        }
+
+        glGetIntegerv(GL_READ_BUFFER, &buffer);
+        if (buffer == GL_NONE) {
+            glReadBuffer(GL_BACK);
+        }
+
+        glBindFramebuffer(GL_READ_FRAMEBUFFER, curReadFB);
+        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, curDrawFB);
+
+        GL_CHECKPOINT(LOW);
+    }
+
     return error ? MakeCurrentResult::Failed : MakeCurrentResult::Succeeded;
 }
 
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index 23611ef..7e9d44f 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -117,12 +117,8 @@
     // flush and submit all work to the gpu and wait for it to finish
     mGrContext->flushAndSubmit(/*syncCpu=*/true);
 
-    if (!Properties::isHighEndGfx && mode >= TrimLevel::MODERATE) {
-        mode = TrimLevel::COMPLETE;
-    }
-
     switch (mode) {
-        case TrimLevel::COMPLETE:
+        case TrimLevel::BACKGROUND:
             mGrContext->freeGpuResources();
             SkGraphics::PurgeAllCaches();
             mRenderThread.destroyRenderingContext();
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 718d4a1..96bfc10 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -42,6 +42,36 @@
 namespace uirenderer {
 namespace renderthread {
 
+static std::array<std::string_view, 18> sEnableExtensions{
+        VK_KHR_BIND_MEMORY_2_EXTENSION_NAME,
+        VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME,
+        VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME,
+        VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME,
+        VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME,
+        VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME,
+        VK_KHR_MAINTENANCE1_EXTENSION_NAME,
+        VK_KHR_MAINTENANCE2_EXTENSION_NAME,
+        VK_KHR_MAINTENANCE3_EXTENSION_NAME,
+        VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME,
+        VK_KHR_SURFACE_EXTENSION_NAME,
+        VK_KHR_SWAPCHAIN_EXTENSION_NAME,
+        VK_EXT_BLEND_OPERATION_ADVANCED_EXTENSION_NAME,
+        VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME,
+        VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME,
+        VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME,
+        VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME,
+        VK_KHR_ANDROID_SURFACE_EXTENSION_NAME,
+};
+
+static bool shouldEnableExtension(const std::string_view& extension) {
+    for (const auto& it : sEnableExtensions) {
+        if (it == extension) {
+            return true;
+        }
+    }
+    return false;
+}
+
 static void free_features_extensions_structs(const VkPhysicalDeviceFeatures2& features) {
     // All Vulkan structs that could be part of the features chain will start with the
     // structure type followed by the pNext pointer. We cast to the CommonVulkanHeader
@@ -139,6 +169,11 @@
         bool hasKHRSurfaceExtension = false;
         bool hasKHRAndroidSurfaceExtension = false;
         for (const VkExtensionProperties& extension : mInstanceExtensionsOwner) {
+            if (!shouldEnableExtension(extension.extensionName)) {
+                ALOGV("Not enabling instance extension %s", extension.extensionName);
+                continue;
+            }
+            ALOGV("Enabling instance extension %s", extension.extensionName);
             mInstanceExtensions.push_back(extension.extensionName);
             if (!strcmp(extension.extensionName, VK_KHR_SURFACE_EXTENSION_NAME)) {
                 hasKHRSurfaceExtension = true;
@@ -219,6 +254,11 @@
         LOG_ALWAYS_FATAL_IF(VK_SUCCESS != err);
         bool hasKHRSwapchainExtension = false;
         for (const VkExtensionProperties& extension : mDeviceExtensionsOwner) {
+            if (!shouldEnableExtension(extension.extensionName)) {
+                ALOGV("Not enabling device extension %s", extension.extensionName);
+                continue;
+            }
+            ALOGV("Enabling device extension %s", extension.extensionName);
             mDeviceExtensions.push_back(extension.extensionName);
             if (!strcmp(extension.extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME)) {
                 hasKHRSwapchainExtension = true;
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index b1d2e33..4759689 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -3730,7 +3730,12 @@
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     @RequiresPermission(Manifest.permission.BLUETOOTH_STACK)
     public void setA2dpSuspended(boolean enable) {
-        AudioSystem.setParameters("A2dpSuspended=" + enable);
+        final IAudioService service = getService();
+        try {
+            service.setA2dpSuspended(enable);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -3743,7 +3748,12 @@
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     @RequiresPermission(Manifest.permission.BLUETOOTH_STACK)
     public void setLeAudioSuspended(boolean enable) {
-        AudioSystem.setParameters("LeAudioSuspended=" + enable);
+        final IAudioService service = getService();
+        try {
+            service.setLeAudioSuspended(enable);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index e73cf87..3123ee6 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -1237,6 +1237,9 @@
     public static final Set<Integer> DEVICE_IN_ALL_SCO_SET;
     /** @hide */
     public static final Set<Integer> DEVICE_IN_ALL_USB_SET;
+    /** @hide */
+    public static final Set<Integer> DEVICE_IN_ALL_BLE_SET;
+
     static {
         DEVICE_IN_ALL_SET = new HashSet<>();
         DEVICE_IN_ALL_SET.add(DEVICE_IN_COMMUNICATION);
@@ -1276,6 +1279,66 @@
         DEVICE_IN_ALL_USB_SET.add(DEVICE_IN_USB_ACCESSORY);
         DEVICE_IN_ALL_USB_SET.add(DEVICE_IN_USB_DEVICE);
         DEVICE_IN_ALL_USB_SET.add(DEVICE_IN_USB_HEADSET);
+
+        DEVICE_IN_ALL_BLE_SET = new HashSet<>();
+        DEVICE_IN_ALL_BLE_SET.add(DEVICE_IN_BLE_HEADSET);
+    }
+
+    /** @hide */
+    public static boolean isBluetoothDevice(int deviceType) {
+        return isBluetoothA2dpOutDevice(deviceType)
+                || isBluetoothScoDevice(deviceType)
+                || isBluetoothLeDevice(deviceType);
+    }
+
+    /** @hide */
+    public static boolean isBluetoothOutDevice(int deviceType) {
+        return isBluetoothA2dpOutDevice(deviceType)
+                || isBluetoothScoOutDevice(deviceType)
+                || isBluetoothLeOutDevice(deviceType);
+    }
+
+    /** @hide */
+    public static boolean isBluetoothInDevice(int deviceType) {
+        return isBluetoothScoInDevice(deviceType)
+                || isBluetoothLeInDevice(deviceType);
+    }
+
+    /** @hide */
+    public static boolean isBluetoothA2dpOutDevice(int deviceType) {
+        return DEVICE_OUT_ALL_A2DP_SET.contains(deviceType);
+    }
+
+    /** @hide */
+    public static boolean isBluetoothScoOutDevice(int deviceType) {
+        return DEVICE_OUT_ALL_SCO_SET.contains(deviceType);
+    }
+
+    /** @hide */
+    public static boolean isBluetoothScoInDevice(int deviceType) {
+        return DEVICE_IN_ALL_SCO_SET.contains(deviceType);
+    }
+
+    /** @hide */
+    public static boolean isBluetoothScoDevice(int deviceType) {
+        return isBluetoothScoOutDevice(deviceType)
+                || isBluetoothScoInDevice(deviceType);
+    }
+
+    /** @hide */
+    public static boolean isBluetoothLeOutDevice(int deviceType) {
+        return DEVICE_OUT_ALL_BLE_SET.contains(deviceType);
+    }
+
+    /** @hide */
+    public static boolean isBluetoothLeInDevice(int deviceType) {
+        return DEVICE_IN_ALL_BLE_SET.contains(deviceType);
+    }
+
+    /** @hide */
+    public static boolean isBluetoothLeDevice(int deviceType) {
+        return isBluetoothLeOutDevice(deviceType)
+                || isBluetoothLeInDevice(deviceType);
     }
 
     /** @hide */
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index fe5afc5..7ce189b 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -231,6 +231,12 @@
 
     void setBluetoothScoOn(boolean on);
 
+    @EnforcePermission("BLUETOOTH_STACK")
+    void setA2dpSuspended(boolean on);
+
+    @EnforcePermission("BLUETOOTH_STACK")
+    void setLeAudioSuspended(boolean enable);
+
     boolean isBluetoothScoOn();
 
     void setBluetoothA2dpOn(boolean on);
diff --git a/media/java/android/media/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
index 1a3e54d..afa0a32 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerDetector.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
@@ -391,15 +391,6 @@
          * @hide
          */
         @Override
-        public void onError(int status) {
-            Slog.d(TAG, "onError()" + status);
-            mHandler.sendEmptyMessage(MSG_DETECTION_ERROR);
-        }
-
-        /**
-         * @hide
-         */
-        @Override
         public void onRecognitionPaused() {
             Slog.d(TAG, "onRecognitionPaused()");
             mHandler.sendEmptyMessage(MSG_DETECTION_PAUSE);
@@ -413,6 +404,42 @@
             Slog.d(TAG, "onRecognitionResumed()");
             mHandler.sendEmptyMessage(MSG_DETECTION_RESUME);
         }
+
+        /**
+         * @hide
+         */
+        @Override
+        public void onPreempted() {
+            Slog.d(TAG, "onPreempted()");
+            mHandler.sendEmptyMessage(MSG_DETECTION_ERROR);
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        public void onModuleDied() {
+            Slog.d(TAG, "onModuleDied()");
+            mHandler.sendEmptyMessage(MSG_DETECTION_ERROR);
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        public void onResumeFailed(int status) {
+            Slog.d(TAG, "onResumeFailed()" + status);
+            mHandler.sendEmptyMessage(MSG_DETECTION_ERROR);
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        public void onPauseFailed(int status) {
+            Slog.d(TAG, "onPauseFailed()" + status);
+            mHandler.sendEmptyMessage(MSG_DETECTION_ERROR);
+        }
     }
 
     private class MyHandler extends Handler {
diff --git a/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java b/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java
new file mode 100644
index 0000000..80bc5c0
--- /dev/null
+++ b/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java
@@ -0,0 +1,630 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.soundtrigger;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.TestApi;
+import android.hardware.soundtrigger.ConversionUtil;
+import android.hardware.soundtrigger.SoundTrigger;
+import android.media.soundtrigger_middleware.IAcknowledgeEvent;
+import android.media.soundtrigger_middleware.IInjectGlobalEvent;
+import android.media.soundtrigger_middleware.IInjectModelEvent;
+import android.media.soundtrigger_middleware.IInjectRecognitionEvent;
+import android.media.soundtrigger_middleware.ISoundTriggerInjection;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.ISoundTriggerService;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Used to inject/observe events when using a fake SoundTrigger HAL for test purposes.
+ * Created by {@link SoundTriggerManager#getInjection(Executor, GlobalCallback)}.
+ * Only one instance of this class is valid at any given time, old instances will be delivered
+ * {@link GlobalCallback#onPreempted()}.
+ * @hide
+ */
+@TestApi
+public final class SoundTriggerInstrumentation {
+
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private IInjectGlobalEvent mInjectGlobalEvent = null;
+
+    @GuardedBy("mLock")
+    private Map<IBinder, ModelSession> mModelSessionMap = new HashMap<>();
+    @GuardedBy("mLock")
+    private Map<IBinder, RecognitionSession> mRecognitionSessionMap = new HashMap<>();
+    @GuardedBy("mLock")
+    private IBinder mClientToken = null;
+
+    private final GlobalCallback mClientCallback;
+    private final Executor mGlobalCallbackExecutor;
+
+    /**
+     * Callback interface for un-sessioned events observed from the fake STHAL.
+     * Registered upon construction of {@link SoundTriggerInstrumentation}
+     * @hide
+     */
+    @TestApi
+    public interface GlobalCallback {
+        /**
+         * Called when the created {@link SoundTriggerInstrumentation} object is invalidated
+         * by another client creating an {@link SoundTriggerInstrumentation} to instrument the
+         * fake STHAL. Only one client may inject at a time.
+         * All sessions are invalidated, no further events will be received, and no
+         * injected events will be delivered.
+         */
+        default void onPreempted() {}
+        /**
+         * Called when the STHAL has been restarted by the framework, due to unexpected
+         * error conditions.
+         * Not called when {@link SoundTriggerInstrumentation#triggerRestart()} is injected.
+         */
+        default void onRestarted() {}
+        /**
+         * Called when the framework detaches from the fake HAL.
+         * This is not transmitted to real HALs, but it indicates that the
+         * framework has flushed its global state.
+         */
+        default void onFrameworkDetached() {}
+        /**
+         * Called when a client application attaches to the framework.
+         * This is not transmitted to real HALs, but it represents the state of
+         * the framework.
+         */
+        default void onClientAttached() {}
+        /**
+         * Called when a client application detaches from the framework.
+         * This is not transmitted to real HALs, but it represents the state of
+         * the framework.
+         */
+        default void onClientDetached() {}
+        /**
+         * Called when the fake HAL receives a model load from the framework.
+         * @param modelSession - A session which exposes additional injection
+         *                       functionality associated with the newly loaded
+         *                       model. See {@link ModelSession}.
+         */
+        void onModelLoaded(@NonNull ModelSession modelSession);
+    }
+
+    /**
+     * Callback for HAL events related to a loaded model. Register with
+     * {@link ModelSession#setModelCallback(Executor, ModelCallback)}
+     * Note, callbacks will not be delivered for events triggered by the injection.
+     * @hide
+     */
+    @TestApi
+    public interface ModelCallback {
+        /**
+         * Called when the model associated with the {@link ModelSession} this callback
+         * was registered for was unloaded by the framework.
+         */
+        default void onModelUnloaded() {}
+        /**
+         * Called when the model associated with the {@link ModelSession} this callback
+         * was registered for receives a set parameter call from the framework.
+         * @param param - Parameter being set.
+         *                 See {@link SoundTrigger.ModelParamTypes}
+         * @param value - Value the model parameter was set to.
+         */
+        default void onParamSet(@SoundTrigger.ModelParamTypes int param, int value) {}
+        /**
+         * Called when the model associated with the {@link ModelSession} this callback
+         * was registered for receives a recognition start request.
+         * @param recognitionSession - A session which exposes additional injection
+         *                             functionality associated with the newly started
+         *                             recognition. See {@link RecognitionSession}
+         */
+        void onRecognitionStarted(@NonNull RecognitionSession recognitionSession);
+    }
+
+    /**
+     * Callback for HAL events related to a started recognition. Register with
+     * {@link RecognitionSession#setRecognitionCallback(Executor, RecognitionCallback)}
+     * Note, callbacks will not be delivered for events triggered by the injection.
+     * @hide
+     */
+    @TestApi
+    public interface RecognitionCallback {
+        /**
+         * Called when the recognition associated with the {@link RecognitionSession} this
+         * callback was registered for was stopped by the framework.
+         */
+        void onRecognitionStopped();
+    }
+
+    /**
+     * Session associated with a loaded model in the fake STHAL.
+     * Can be used to query details about the loaded model, register a callback for future
+     * model events, or trigger HAL events associated with a loaded model.
+     * This session is invalid once the model is unloaded, caused by a
+     * {@link ModelSession#triggerUnloadModel()},
+     * the client unloading recognition, or if a {@link GlobalCallback#onRestarted()} is
+     * received.
+     * Further injections on an invalidated session will not be respected, and no future
+     * callbacks will be delivered.
+     * @hide
+     */
+    @TestApi
+    public class ModelSession {
+
+        /**
+         * Trigger the HAL to preemptively unload the model associated with this session.
+         * Typically occurs when a higher priority model is loaded which utilizes the same
+         * resources.
+         */
+        public void triggerUnloadModel() {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                try {
+                    mInjectModelEvent.triggerUnloadModel();
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+                mModelSessionMap.remove(mInjectModelEvent.asBinder());
+            }
+        }
+
+        /**
+         * Get the {@link SoundTriggerManager.Model} associated with this session.
+         * @return - The model associated with this session.
+         */
+        public @NonNull SoundTriggerManager.Model getSoundModel() {
+            return mModel;
+        }
+
+        /**
+         * Get the list of {@link SoundTrigger.Keyphrase} associated with this session.
+         * @return - The keyphrases associated with this session.
+         */
+        public @NonNull List<SoundTrigger.Keyphrase> getPhrases() {
+            if (mPhrases == null) {
+                return new ArrayList<>();
+            } else {
+                return new ArrayList<>(Arrays.asList(mPhrases));
+            }
+        }
+
+        /**
+         * Get whether this model is of keyphrase type.
+         * @return - true if the model is a keyphrase model, false otherwise
+         */
+        public boolean isKeyphrase() {
+            return (mPhrases != null);
+        }
+
+        /**
+         * Registers the model callback associated with this session. Events associated
+         * with this model session will be reported via this callback.
+         * See {@link ModelCallback}
+         * @param executor - Executor which the callback is dispatched on
+         * @param callback - Model callback for reporting model session events.
+         */
+        public void setModelCallback(@NonNull @CallbackExecutor Executor executor, @NonNull
+                ModelCallback callback) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                mModelCallback = Objects.requireNonNull(callback);
+                mModelExecutor = Objects.requireNonNull(executor);
+            }
+        }
+
+        /**
+         * Clear the model callback associated with this session, if any has been
+         * set by {@link #setModelCallback(Executor, ModelCallback)}.
+         */
+        public void clearModelCallback() {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                mModelCallback = null;
+                mModelExecutor = null;
+            }
+        }
+
+        private ModelSession(SoundModel model, Phrase[] phrases,
+                IInjectModelEvent injection) {
+            mModel = SoundTriggerManager.Model.create(UUID.fromString(model.uuid),
+                    UUID.fromString(model.vendorUuid),
+                    ConversionUtil.sharedMemoryToByteArray(model.data, model.dataSize));
+            if (phrases != null) {
+                mPhrases = new SoundTrigger.Keyphrase[phrases.length];
+                int i = 0;
+                for (var phrase : phrases) {
+                    mPhrases[i++] = ConversionUtil.aidl2apiPhrase(phrase);
+                }
+            } else {
+                mPhrases = null;
+            }
+            mInjectModelEvent = injection;
+        }
+
+        private void wrap(Consumer<ModelCallback> consumer) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                if (mModelCallback != null && mModelExecutor != null) {
+                    final ModelCallback callback = mModelCallback;
+                    mModelExecutor.execute(() -> consumer.accept(callback));
+                }
+            }
+        }
+
+        private final SoundTriggerManager.Model mModel;
+        private final SoundTrigger.Keyphrase[] mPhrases;
+        private final IInjectModelEvent mInjectModelEvent;
+
+        @GuardedBy("SoundTriggerInstrumentation.this.mLock")
+        private ModelCallback mModelCallback = null;
+        @GuardedBy("SoundTriggerInstrumentation.this.mLock")
+        private Executor mModelExecutor = null;
+    }
+
+    /**
+     * Session associated with a recognition start in the fake STHAL.
+     * Can be used to get information about the started recognition, register a callback
+     * for future events associated with this recognition, and triggering
+     * recognition events or aborts.
+     * This session is invalid once the recognition is stopped, caused by a
+     * {@link RecognitionSession#triggerAbortRecognition()},
+     * {@link RecognitionSession#triggerRecognitionEvent(byte[], List)},
+     * the client stopping recognition, or any operation which invalidates the
+     * {@link ModelSession} which the session was created from.
+     * Further injections on an invalidated session will not be respected, and no future
+     * callbacks will be delivered.
+     * @hide
+     */
+    @TestApi
+    public class RecognitionSession {
+
+        /**
+         * Get an integer token representing the audio session associated with this
+         * recognition in the STHAL.
+         * @return - The session token.
+         */
+        public int getAudioSession() {
+            return mAudioSession;
+        }
+
+        /**
+         * Get the recognition config used to start this recognition.
+         * @return - The config passed to the HAL for startRecognition.
+         */
+        public @NonNull SoundTrigger.RecognitionConfig getRecognitionConfig() {
+            return mRecognitionConfig;
+        }
+
+        /**
+         * Trigger a recognition in the fake STHAL.
+         * @param data - The opaque data buffer included in the recognition event.
+         * @param phraseExtras - Keyphrase metadata included in the event. The
+         *                       event must include metadata for the keyphrase id
+         *                       associated with this model to be received by the
+         *                       client application.
+         */
+        public void triggerRecognitionEvent(@NonNull byte[] data, @Nullable
+                List<SoundTrigger.KeyphraseRecognitionExtra> phraseExtras) {
+            PhraseRecognitionExtra[] converted = null;
+            if (phraseExtras != null) {
+                converted = new PhraseRecognitionExtra[phraseExtras.size()];
+                int i = 0;
+                for (var phraseExtra : phraseExtras) {
+                    converted[i++] = ConversionUtil.api2aidlPhraseRecognitionExtra(phraseExtra);
+                }
+            }
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                mRecognitionSessionMap.remove(mInjectRecognitionEvent.asBinder());
+                try {
+                    mInjectRecognitionEvent.triggerRecognitionEvent(data, converted);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+        }
+
+        /**
+         * Trigger an abort recognition event in the fake HAL. This represents a
+         * preemptive ending of the recognition session by the HAL, despite no
+         * recognition detection. Typically occurs during contention for microphone
+         * usage, or if model limits are hit.
+         * See {@link SoundTriggerInstrumentation#setResourceContention(boolean)} to block
+         * subsequent downward calls for contention reasons.
+         */
+        public void triggerAbortRecognition() {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                mRecognitionSessionMap.remove(mInjectRecognitionEvent.asBinder());
+                try {
+                    mInjectRecognitionEvent.triggerAbortRecognition();
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+        }
+
+         /**
+         * Registers the recognition callback associated with this session. Events associated
+         * with this recognition session will be reported via this callback.
+         * See {@link RecognitionCallback}
+         * @param executor - Executor which the callback is dispatched on
+         * @param callback - Recognition callback for reporting recognition session events.
+         */
+        public void setRecognitionCallback(@NonNull @CallbackExecutor Executor executor,
+                @NonNull RecognitionCallback callback) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                mRecognitionCallback = callback;
+                mRecognitionExecutor = executor;
+            }
+        }
+
+        /**
+         * Clear the recognition callback associated with this session, if any has been
+         * set by {@link #setRecognitionCallback(Executor, RecognitionCallback)}.
+         */
+        public void clearRecognitionCallback() {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                mRecognitionCallback = null;
+                mRecognitionExecutor = null;
+            }
+        }
+
+        private RecognitionSession(int audioSession,
+                RecognitionConfig recognitionConfig,
+                IInjectRecognitionEvent injectRecognitionEvent) {
+            mAudioSession = audioSession;
+            mRecognitionConfig = ConversionUtil.aidl2apiRecognitionConfig(recognitionConfig);
+            mInjectRecognitionEvent = injectRecognitionEvent;
+        }
+
+        private void wrap(Consumer<RecognitionCallback> consumer) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                if (mRecognitionCallback != null && mRecognitionExecutor != null) {
+                    final RecognitionCallback callback = mRecognitionCallback;
+                    mRecognitionExecutor.execute(() -> consumer.accept(callback));
+                }
+            }
+        }
+
+        private final int mAudioSession;
+        private final SoundTrigger.RecognitionConfig mRecognitionConfig;
+        private final IInjectRecognitionEvent mInjectRecognitionEvent;
+
+        @GuardedBy("SoundTriggerInstrumentation.this.mLock")
+        private Executor mRecognitionExecutor = null;
+        @GuardedBy("SoundTriggerInstrumentation.this.mLock")
+        private RecognitionCallback mRecognitionCallback = null;
+    }
+
+    // Implementation of injection interface passed to the HAL.
+    // This class will re-associate events received on this callback interface
+    // with sessions, to avoid staleness issues.
+    private class Injection extends ISoundTriggerInjection.Stub {
+        @Override
+        public void registerGlobalEventInjection(IInjectGlobalEvent globalInjection) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                mInjectGlobalEvent = globalInjection;
+            }
+        }
+
+        @Override
+        public void onSoundModelLoaded(SoundModel model, @Nullable Phrase[] phrases,
+                            IInjectModelEvent modelInjection, IInjectGlobalEvent globalSession) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                if (globalSession.asBinder() != mInjectGlobalEvent.asBinder()) return;
+                ModelSession modelSession = new ModelSession(model, phrases, modelInjection);
+                mModelSessionMap.put(modelInjection.asBinder(), modelSession);
+                mGlobalCallbackExecutor.execute(() -> mClientCallback.onModelLoaded(modelSession));
+            }
+        }
+
+        @Override
+        public void onSoundModelUnloaded(IInjectModelEvent modelSession) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                ModelSession clientModelSession = mModelSessionMap.remove(modelSession.asBinder());
+                if (clientModelSession == null) return;
+                clientModelSession.wrap((ModelCallback cb) -> cb.onModelUnloaded());
+            }
+        }
+
+        @Override
+        public void onRecognitionStarted(int audioSessionHandle, RecognitionConfig config,
+                IInjectRecognitionEvent recognitionInjection, IInjectModelEvent modelSession) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                ModelSession clientModelSession = mModelSessionMap.get(modelSession.asBinder());
+                if (clientModelSession == null) return;
+                RecognitionSession recogSession = new RecognitionSession(
+                        audioSessionHandle, config, recognitionInjection);
+                mRecognitionSessionMap.put(recognitionInjection.asBinder(), recogSession);
+                clientModelSession.wrap((ModelCallback cb) ->
+                        cb.onRecognitionStarted(recogSession));
+            }
+        }
+
+        @Override
+        public void onRecognitionStopped(IInjectRecognitionEvent recognitionSession) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                RecognitionSession clientRecognitionSession =
+                        mRecognitionSessionMap.remove(recognitionSession.asBinder());
+                if (clientRecognitionSession == null) return;
+                clientRecognitionSession.wrap((RecognitionCallback cb)
+                        -> cb.onRecognitionStopped());
+            }
+        }
+
+        @Override
+        public void onParamSet(int modelParam, int value, IInjectModelEvent modelSession) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                ModelSession clientModelSession = mModelSessionMap.get(modelSession.asBinder());
+                if (clientModelSession == null) return;
+                clientModelSession.wrap((ModelCallback cb) -> cb.onParamSet(modelParam, value));
+            }
+        }
+
+
+        @Override
+        public void onRestarted(IInjectGlobalEvent globalSession) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                if (globalSession.asBinder() != mInjectGlobalEvent.asBinder()) return;
+                mRecognitionSessionMap.clear();
+                mModelSessionMap.clear();
+                mGlobalCallbackExecutor.execute(() -> mClientCallback.onRestarted());
+            }
+        }
+
+        @Override
+        public void onFrameworkDetached(IInjectGlobalEvent globalSession) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                if (globalSession.asBinder() != mInjectGlobalEvent.asBinder()) return;
+                mGlobalCallbackExecutor.execute(() -> mClientCallback.onFrameworkDetached());
+            }
+        }
+
+        @Override
+        public void onClientAttached(IBinder token, IInjectGlobalEvent globalSession) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                if (globalSession.asBinder() != mInjectGlobalEvent.asBinder()) return;
+                mClientToken = token;
+                mGlobalCallbackExecutor.execute(() -> mClientCallback.onClientAttached());
+            }
+        }
+
+        @Override
+        public void onClientDetached(IBinder token) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                if (token != mClientToken) return;
+                mClientToken = null;
+                mGlobalCallbackExecutor.execute(() -> mClientCallback.onClientDetached());
+            }
+        }
+
+        @Override
+        public void onPreempted() {
+            // This is always valid, independent of session
+            mGlobalCallbackExecutor.execute(() -> mClientCallback.onPreempted());
+            // Callbacks will no longer be delivered, and injection will be silently dropped.
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+    public SoundTriggerInstrumentation(ISoundTriggerService service,
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull GlobalCallback callback) {
+        mClientCallback = Objects.requireNonNull(callback);
+        mGlobalCallbackExecutor = Objects.requireNonNull(executor);
+        try {
+            service.attachInjection(new Injection());
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Simulate a HAL restart, typically caused by the framework on an unexpected error,
+     * or a restart of the core audio HAL.
+     * Application sessions will be detached, and all state will be cleared. The framework
+     * will re-attach to the HAL following restart.
+     * @hide
+     */
+    @TestApi
+    public void triggerRestart() {
+        synchronized (mLock) {
+            if (mInjectGlobalEvent == null) {
+                throw new IllegalStateException(
+                        "Attempted to trigger HAL restart before registration");
+            }
+            try {
+                mInjectGlobalEvent.triggerRestart();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Trigger a resource available callback from the fake SoundTrigger HAL to the framework.
+     * This callback notifies the framework that methods which previously failed due to
+     * resource contention may now succeed.
+     * @hide
+     */
+    @TestApi
+    public void triggerOnResourcesAvailable() {
+        synchronized (mLock) {
+            if (mInjectGlobalEvent == null) {
+                throw new IllegalStateException(
+                        "Attempted to trigger HAL resources available before registration");
+            }
+            try {
+                mInjectGlobalEvent.triggerOnResourcesAvailable();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Simulate resource contention, similar to when HAL which does not
+     * support concurrent capture opens a capture stream, or when a HAL
+     * has reached its maximum number of models.
+     * Subsequent model loads and recognition starts will gracefully error.
+     * Since this call does not trigger a callback through the framework, the
+     * call will block until the fake HAL has acknowledged the state change.
+     * @param isResourceContended - true to enable contention, false to return
+     *                              to normal functioning.
+     * @hide
+     */
+    @TestApi
+    public void setResourceContention(boolean isResourceContended) {
+        synchronized (mLock) {
+            if (mInjectGlobalEvent == null) {
+                throw new IllegalStateException("Injection interface not set up");
+            }
+            IInjectGlobalEvent current = mInjectGlobalEvent;
+            final CountDownLatch signal = new CountDownLatch(1);
+            try {
+                current.setResourceContention(isResourceContended, new IAcknowledgeEvent.Stub() {
+                    @Override
+                    public void eventReceived() {
+                        signal.countDown();
+                    }
+                });
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+
+            // Block until we get a callback from the service that our request was serviced.
+            try {
+                // Rely on test timeout if we don't get a response.
+                signal.await();
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+}
+
diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
index ae8121a..c41bd1b 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerManager.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -18,11 +18,13 @@
 
 import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.app.ActivityThread;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
@@ -45,6 +47,7 @@
 import android.os.IBinder;
 import android.os.ParcelUuid;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.provider.Settings;
 import android.util.Slog;
 
@@ -53,9 +56,9 @@
 import com.android.internal.util.Preconditions;
 
 import java.util.HashMap;
-import java.util.List;
 import java.util.Objects;
 import java.util.UUID;
+import java.util.concurrent.Executor;
 
 /**
  * This class provides management of non-voice (general sound trigger) based sound recognition
@@ -609,4 +612,24 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Create a {@link SoundTriggerInstrumentation} for test purposes, which instruments a fake
+     * STHAL. Clients must attach to the appropriate underlying ST module.
+     * @param executor - Executor to dispatch global callbacks on
+     * @param callback - Callback for unsessioned events received by the fake STHAL
+     * @return - A {@link SoundTriggerInstrumentation} for observation/injection.
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+    @NonNull
+    public static SoundTriggerInstrumentation attachInstrumentation(
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull SoundTriggerInstrumentation.GlobalCallback callback) {
+        ISoundTriggerService service = ISoundTriggerService.Stub.asInterface(
+                    ServiceManager.getService(Context.SOUND_TRIGGER_SERVICE));
+        return new SoundTriggerInstrumentation(service, executor, callback);
+    }
+
 }
diff --git a/media/java/android/media/voice/KeyphraseModelManager.java b/media/java/android/media/voice/KeyphraseModelManager.java
index 8ec8967..5a690a5 100644
--- a/media/java/android/media/voice/KeyphraseModelManager.java
+++ b/media/java/android/media/voice/KeyphraseModelManager.java
@@ -21,7 +21,9 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.hardware.soundtrigger.SoundTrigger;
+import android.os.Binder;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.util.Slog;
@@ -154,4 +156,23 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Override the persistent enrolled model database with an in-memory
+     * fake for testing purposes.
+     *
+     * @param enabled - {@code true} if the model enrollment database should be overridden with an
+     * in-memory fake. {@code false} if the real, persistent model enrollment database should be
+     * used.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES)
+    @TestApi
+    public void setModelDatabaseForTestEnabled(boolean enabled) {
+        try {
+            mVoiceInteractionManagerService.setModelDatabaseForTestEnabled(enabled, new Binder());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/media/jni/android_media_MediaExtractor.cpp b/media/jni/android_media_MediaExtractor.cpp
index 116237f..609c7a4 100644
--- a/media/jni/android_media_MediaExtractor.cpp
+++ b/media/jni/android_media_MediaExtractor.cpp
@@ -196,6 +196,15 @@
         dstSize = (size_t) env->GetDirectBufferCapacity(byteBuf);
     }
 
+    // unlikely, but GetByteArrayElements() can fail
+    if (dst == nullptr) {
+        ALOGE("no buffer into which to read the data");
+        if (byteArray != NULL) {
+            env->ReleaseByteArrayElements(byteArray, (jbyte *)dst, 0);
+        }
+        return -ENOMEM;
+    }
+
     if (dstSize < offset) {
         if (byteArray != NULL) {
             env->ReleaseByteArrayElements(byteArray, (jbyte *)dst, 0);
@@ -204,8 +213,10 @@
         return -ERANGE;
     }
 
+    // passes in the backing memory to use, so it doesn't fail
     sp<ABuffer> buffer = new ABuffer((char *)dst + offset, dstSize - offset);
 
+    buffer->setRange(0, 0);  // mark it empty
     status_t err = mImpl->readSampleData(buffer);
 
     if (byteArray != NULL) {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index e53e956..a9bee03 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -107,7 +107,7 @@
 
         initialUiState = when (requestInfo?.type) {
             RequestInfo.TYPE_CREATE -> {
-                val defaultProviderId = userConfigRepo.getDefaultProviderId()
+                val defaultProviderIdSetByUser = userConfigRepo.getDefaultProviderId()
                 val isPasskeyFirstUse = userConfigRepo.getIsPasskeyFirstUse()
                 val providerEnableListUiState = getCreateProviderEnableListInitialUiState()
                 val providerDisableListUiState = getCreateProviderDisableListInitialUiState()
@@ -115,12 +115,14 @@
                     getCreateRequestDisplayInfoInitialUiState(originName)!!
                 UiState(
                     createCredentialUiState = CreateFlowUtils.toCreateCredentialUiState(
-                        providerEnableListUiState,
-                        providerDisableListUiState,
-                        defaultProviderId,
-                        requestDisplayInfoUiState,
+                        enabledProviders = providerEnableListUiState,
+                        disabledProviders = providerDisableListUiState,
+                        defaultProviderIdPreferredByApp =
+                        requestDisplayInfoUiState.appPreferredDefaultProviderId,
+                        defaultProviderIdSetByUser = defaultProviderIdSetByUser,
+                        requestDisplayInfo = requestDisplayInfoUiState,
                         isOnPasskeyIntroStateAlready = false,
-                        isPasskeyFirstUse
+                        isPasskeyFirstUse = isPasskeyFirstUse,
                     )!!,
                     getCredentialUiState = null,
                     cancelRequestState = cancelUiRequestState
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 24f92c0..7581b5c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -19,9 +19,9 @@
 import android.content.Intent
 import android.credentials.ui.BaseDialogResult
 import android.credentials.ui.RequestInfo
+import android.net.Uri
 import android.os.Bundle
 import android.os.ResultReceiver
-import android.provider.Settings
 import android.util.Log
 import androidx.activity.ComponentActivity
 import androidx.activity.compose.rememberLauncherForActivityResult
@@ -192,7 +192,9 @@
             this@CredentialSelectorActivity.finish()
         } else if (dialogState == DialogState.CANCELED_FOR_SETTINGS) {
             Log.d(Constants.LOG_TAG, "Received signal to finish the activity and launch settings.")
-            this@CredentialSelectorActivity.startActivity(Intent(Settings.ACTION_SYNC_SETTINGS))
+            val settingsIntent = Intent(ACTION_CREDENTIAL_PROVIDER)
+            settingsIntent.data = Uri.parse("package:" + this.getPackageName())
+            this@CredentialSelectorActivity.startActivity(settingsIntent)
             this@CredentialSelectorActivity.finish()
         }
     }
@@ -222,4 +224,8 @@
             dismissOnTimeout = true,
         )
     }
+
+    companion object {
+        const val ACTION_CREDENTIAL_PROVIDER = "android.settings.CREDENTIAL_PROVIDER"
+    }
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 29ec970..4d2bb4c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -250,9 +250,15 @@
             return
         }
         val newUiState = CreateFlowUtils.toCreateCredentialUiState(
-            prevUiState.enabledProviders, prevUiState.disabledProviders,
-            userConfigRepo.getDefaultProviderId(), prevUiState.requestDisplayInfo, true,
-            userConfigRepo.getIsPasskeyFirstUse())
+            enabledProviders = prevUiState.enabledProviders,
+            disabledProviders = prevUiState.disabledProviders,
+            defaultProviderIdPreferredByApp =
+            prevUiState.requestDisplayInfo.appPreferredDefaultProviderId,
+            defaultProviderIdSetByUser = userConfigRepo.getDefaultProviderId(),
+            requestDisplayInfo = prevUiState.requestDisplayInfo,
+            isOnPasskeyIntroStateAlready = true,
+            isPasskeyFirstUse = userConfigRepo.getIsPasskeyFirstUse()
+        )
         if (newUiState == null) {
             Log.d(Constants.LOG_TAG, "Unable to update create ui state")
             onInternalError()
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index ca89129..108f494 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -198,7 +198,8 @@
                     it.type,
                     it.credentialRetrievalData,
                     it.credentialRetrievalData,
-                    it.isSystemProviderRequired
+                    it.isSystemProviderRequired,
+                    it.allowedProviders,
                 )
                 if (credentialOptionJetpack is GetPublicKeyCredentialOption) {
                     credentialOptionJetpack.preferImmediatelyAvailableCredentials
@@ -252,13 +253,6 @@
                         ))
                     }
                     is PublicKeyCredentialEntry -> {
-                        val passkeyUsername = credentialEntry.username.toString()
-                        val passkeyDisplayName = credentialEntry.displayName?.toString() ?: ""
-                        val (username, displayName) = userAndDisplayNameForPasskey(
-                            passkeyUsername = passkeyUsername,
-                            passkeyDisplayName = passkeyDisplayName,
-                        )
-
                         result.add(CredentialEntryInfo(
                             providerId = providerId,
                             providerDisplayName = providerLabel,
@@ -268,8 +262,8 @@
                             fillInIntent = it.frameworkExtrasIntent,
                             credentialType = CredentialType.PASSKEY,
                             credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(),
-                            userName = username,
-                            displayName = displayName,
+                            userName = credentialEntry.username.toString(),
+                            displayName = credentialEntry.displayName?.toString(),
                             icon = credentialEntry.icon.loadDrawable(context),
                             shouldTintIcon = credentialEntry.isDefaultIcon,
                             lastUsedTimeMillis = credentialEntry.lastUsedTime,
@@ -469,8 +463,12 @@
                 createCredentialRequest.type,
                 createCredentialRequest.credentialData,
                 createCredentialRequest.candidateQueryData,
-                createCredentialRequest.isSystemProviderRequired
+                createCredentialRequest.isSystemProviderRequired,
+                createCredentialRequest.origin,
             )
+            val appPreferredDefaultProviderId: String? =
+                if (!requestInfo.hasPermissionToOverrideDefault()) null
+                else createCredentialRequestJetpack?.displayInfo?.preferDefaultProvider
             return when (createCredentialRequestJetpack) {
                 is CreatePasswordRequest -> RequestDisplayInfo(
                     createCredentialRequestJetpack.id,
@@ -479,6 +477,7 @@
                     appLabel,
                     context.getDrawable(R.drawable.ic_password_24) ?: return null,
                     preferImmediatelyAvailableCredentials = false,
+                    appPreferredDefaultProviderId = appPreferredDefaultProviderId,
                 )
                 is CreatePublicKeyCredentialRequest -> {
                     newRequestDisplayInfoFromPasskeyJson(
@@ -487,6 +486,7 @@
                         context = context,
                         preferImmediatelyAvailableCredentials =
                         createCredentialRequestJetpack.preferImmediatelyAvailableCredentials,
+                        appPreferredDefaultProviderId = appPreferredDefaultProviderId,
                     )
                 }
                 is CreateCustomCredentialRequest -> {
@@ -502,6 +502,7 @@
                         typeIcon = displayInfo.credentialTypeIcon?.loadDrawable(context)
                             ?: context.getDrawable(R.drawable.ic_other_sign_in_24) ?: return null,
                         preferImmediatelyAvailableCredentials = false,
+                        appPreferredDefaultProviderId = appPreferredDefaultProviderId,
                     )
                 }
                 else -> null
@@ -511,20 +512,27 @@
         fun toCreateCredentialUiState(
             enabledProviders: List<EnabledProviderInfo>,
             disabledProviders: List<DisabledProviderInfo>?,
-            defaultProviderId: String?,
+            defaultProviderIdPreferredByApp: String?,
+            defaultProviderIdSetByUser: String?,
             requestDisplayInfo: RequestDisplayInfo,
             isOnPasskeyIntroStateAlready: Boolean,
             isPasskeyFirstUse: Boolean,
         ): CreateCredentialUiState? {
             var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
             var remoteEntry: RemoteInfo? = null
-            var defaultProvider: EnabledProviderInfo? = null
+            var defaultProviderPreferredByApp: EnabledProviderInfo? = null
+            var defaultProviderSetByUser: EnabledProviderInfo? = null
             var createOptionsPairs:
                 MutableList<Pair<CreateOptionInfo, EnabledProviderInfo>> = mutableListOf()
             enabledProviders.forEach { enabledProvider ->
-                if (defaultProviderId != null) {
-                    if (enabledProvider.id == defaultProviderId) {
-                        defaultProvider = enabledProvider
+                if (defaultProviderIdPreferredByApp != null) {
+                    if (enabledProvider.id == defaultProviderIdPreferredByApp) {
+                        defaultProviderPreferredByApp = enabledProvider
+                    }
+                }
+                if (defaultProviderIdSetByUser != null) {
+                    if (enabledProvider.id == defaultProviderIdSetByUser) {
+                        defaultProviderSetByUser = enabledProvider
                     }
                 }
                 if (enabledProvider.createOptions.isNotEmpty()) {
@@ -543,12 +551,14 @@
                     remoteEntry = currRemoteEntry
                 }
             }
+            val defaultProvider = defaultProviderPreferredByApp ?: defaultProviderSetByUser
             val initialScreenState = toCreateScreenState(
-                /*createOptionSize=*/createOptionsPairs.size,
-                /*isOnPasskeyIntroStateAlready=*/isOnPasskeyIntroStateAlready,
-                /*requestDisplayInfo=*/requestDisplayInfo,
-                /*defaultProvider=*/defaultProvider, /*remoteEntry=*/remoteEntry,
-                /*isPasskeyFirstUse=*/isPasskeyFirstUse
+                createOptionSize = createOptionsPairs.size,
+                isOnPasskeyIntroStateAlready = isOnPasskeyIntroStateAlready,
+                requestDisplayInfo = requestDisplayInfo,
+                defaultProvider = defaultProvider,
+                remoteEntry = remoteEntry,
+                isPasskeyFirstUse = isPasskeyFirstUse
             ) ?: return null
             return CreateCredentialUiState(
                 enabledProviders = enabledProviders,
@@ -674,6 +684,7 @@
             appLabel: String,
             context: Context,
             preferImmediatelyAvailableCredentials: Boolean,
+            appPreferredDefaultProviderId: String?,
         ): RequestDisplayInfo? {
             val json = JSONObject(requestJson)
             var passkeyUsername = ""
@@ -694,6 +705,7 @@
                 appLabel,
                 context.getDrawable(R.drawable.ic_passkey_24) ?: return null,
                 preferImmediatelyAvailableCredentials,
+                appPreferredDefaultProviderId,
             )
         }
     }
@@ -707,7 +719,7 @@
  * 2) username on top if display-name is not available.
  * 3) don't show username on second line if username == display-name
  */
-private fun userAndDisplayNameForPasskey(
+fun userAndDisplayNameForPasskey(
     passkeyUsername: String,
     passkeyDisplayName: String,
 ): Pair<String, String> {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
index 14bf4f2..2df0c7a9 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
@@ -27,8 +27,12 @@
 import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
 
 @Composable
-fun CredentialListSectionHeader(text: String) {
-    InternalSectionHeader(text, LocalAndroidColorScheme.current.colorOnSurfaceVariant)
+fun CredentialListSectionHeader(text: String, isFirstSection: Boolean) {
+    InternalSectionHeader(
+        text = text,
+        color = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+        applyTopPadding = !isFirstSection
+    )
 }
 
 @Composable
@@ -37,8 +41,10 @@
 }
 
 @Composable
-private fun InternalSectionHeader(text: String, color: Color) {
-    Row(modifier = Modifier.fillMaxWidth().wrapContentHeight().padding(top = 8.dp)) {
+private fun InternalSectionHeader(text: String, color: Color, applyTopPadding: Boolean = false) {
+    Row(modifier = Modifier.fillMaxWidth().wrapContentHeight().padding(
+        top = if (applyTopPadding) 8.dp else 0.dp
+    )) {
         SectionHeaderText(
             text,
             modifier = Modifier.padding(top = 20.dp, bottom = 8.dp),
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 66d7db8..96010cc 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -435,7 +435,7 @@
     }
     SheetContainerCard {
         item { HeadlineIcon(imageVector = Icons.Outlined.NewReleases) }
-        item { Divider(thickness = 24.dp, color = Color.Transparent) }
+        item { Divider(thickness = 16.dp, color = Color.Transparent) }
         item {
             HeadlineText(
                 text = stringResource(
@@ -633,6 +633,7 @@
             }
         }
         item {
+            Divider(thickness = 8.dp, color = Color.Transparent)
             MoreAboutPasskeySectionHeader(
                 text = stringResource(R.string.public_key_cryptography_title)
             )
@@ -641,6 +642,7 @@
             }
         }
         item {
+            Divider(thickness = 8.dp, color = Color.Transparent)
             MoreAboutPasskeySectionHeader(
                 text = stringResource(R.string.improved_account_security_title)
             )
@@ -649,6 +651,7 @@
             }
         }
         item {
+            Divider(thickness = 8.dp, color = Color.Transparent)
             MoreAboutPasskeySectionHeader(
                 text = stringResource(R.string.seamless_transition_title)
             )
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 4332fb3..12bb629 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -107,6 +107,7 @@
   val appName: String,
   val typeIcon: Drawable,
   val preferImmediatelyAvailableCredentials: Boolean,
+  val appPreferredDefaultProviderId: String?,
 )
 
 /**
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index c1ea1d8..98bd020 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -66,6 +66,7 @@
 import com.android.credentialmanager.common.ui.LargeLabelTextOnSurfaceVariant
 import com.android.credentialmanager.common.ui.Snackbar
 import com.android.credentialmanager.logging.GetCredentialEvent
+import com.android.credentialmanager.userAndDisplayNameForPasskey
 import com.android.internal.logging.UiEventLogger.UiEventEnum
 
 @Composable
@@ -322,12 +323,15 @@
             bottomPadding = 0.dp,
         )
     }) {
+        var isFirstSection = true
         // For username
         items(sortedUserNameToCredentialEntryList) { item ->
             PerUserNameCredentials(
                 perUserNameCredentialEntryList = item,
                 onEntrySelected = onEntrySelected,
+                isFirstSection = isFirstSection,
             )
+            isFirstSection = false
         }
         // Locked password manager
         if (authenticationEntryList.isNotEmpty()) {
@@ -335,7 +339,9 @@
                 LockedCredentials(
                     authenticationEntryList = authenticationEntryList,
                     onEntrySelected = onEntrySelected,
+                    isFirstSection = isFirstSection,
                 )
+                isFirstSection = false
             }
         }
         // From another device
@@ -345,15 +351,19 @@
                 RemoteEntryCard(
                     remoteEntry = remoteEntry,
                     onEntrySelected = onEntrySelected,
+                    isFirstSection = isFirstSection,
                 )
+                isFirstSection = false
             }
         }
         // Manage sign-ins (action chips)
         item {
             ActionChips(
                 providerInfoList = providerInfoList,
-                onEntrySelected = onEntrySelected
+                onEntrySelected = onEntrySelected,
+                isFirstSection = isFirstSection,
             )
+            isFirstSection = false
         }
     }
     onLog(GetCredentialEvent.CREDMAN_GET_CRED_ALL_SIGN_IN_OPTION_CARD)
@@ -366,6 +376,7 @@
 fun ActionChips(
     providerInfoList: List<ProviderInfo>,
     onEntrySelected: (BaseEntry) -> Unit,
+    isFirstSection: Boolean,
 ) {
     val actionChips = providerInfoList.flatMap { it.actionEntryList }
     if (actionChips.isEmpty()) {
@@ -373,7 +384,8 @@
     }
 
     CredentialListSectionHeader(
-        text = stringResource(R.string.get_dialog_heading_manage_sign_ins)
+        text = stringResource(R.string.get_dialog_heading_manage_sign_ins),
+        isFirstSection = isFirstSection,
     )
     CredentialContainerCard {
         Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
@@ -388,9 +400,11 @@
 fun RemoteEntryCard(
     remoteEntry: RemoteEntryInfo,
     onEntrySelected: (BaseEntry) -> Unit,
+    isFirstSection: Boolean,
 ) {
     CredentialListSectionHeader(
-        text = stringResource(R.string.get_dialog_heading_from_another_device)
+        text = stringResource(R.string.get_dialog_heading_from_another_device),
+        isFirstSection = isFirstSection,
     )
     CredentialContainerCard {
         Column(
@@ -412,9 +426,11 @@
 fun LockedCredentials(
     authenticationEntryList: List<AuthenticationEntryInfo>,
     onEntrySelected: (BaseEntry) -> Unit,
+    isFirstSection: Boolean,
 ) {
     CredentialListSectionHeader(
-        text = stringResource(R.string.get_dialog_heading_locked_password_managers)
+        text = stringResource(R.string.get_dialog_heading_locked_password_managers),
+        isFirstSection = isFirstSection,
     )
     CredentialContainerCard {
         Column(
@@ -432,11 +448,13 @@
 fun PerUserNameCredentials(
     perUserNameCredentialEntryList: PerUserNameCredentialEntryList,
     onEntrySelected: (BaseEntry) -> Unit,
+    isFirstSection: Boolean,
 ) {
     CredentialListSectionHeader(
         text = stringResource(
             R.string.get_dialog_heading_for_username, perUserNameCredentialEntryList.userName
-        )
+        ),
+        isFirstSection = isFirstSection,
     )
     CredentialContainerCard {
         Column(
@@ -457,6 +475,10 @@
     enforceOneLine: Boolean = false,
     onTextLayout: (TextLayoutResult) -> Unit = {},
 ) {
+    val (username, displayName) = if (credentialEntryInfo.credentialType == CredentialType.PASSKEY)
+        userAndDisplayNameForPasskey(
+            credentialEntryInfo.userName, credentialEntryInfo.displayName ?: "")
+    else Pair(credentialEntryInfo.userName, credentialEntryInfo.displayName)
     Entry(
         onClick = { onEntrySelected(credentialEntryInfo) },
         iconImageBitmap = credentialEntryInfo.icon?.toBitmap()?.asImageBitmap(),
@@ -465,13 +487,13 @@
         iconPainter =
         if (credentialEntryInfo.icon == null) painterResource(R.drawable.ic_other_sign_in_24)
         else null,
-        entryHeadlineText = credentialEntryInfo.userName,
+        entryHeadlineText = username,
         entrySecondLineText = if (
             credentialEntryInfo.credentialType == CredentialType.PASSWORD) {
             "••••••••••••"
         } else {
             val itemsToDisplay = listOf(
-                credentialEntryInfo.displayName,
+                displayName,
                 credentialEntryInfo.credentialTypeDisplayName,
                 credentialEntryInfo.providerDisplayName
             ).filterNot(TextUtils::isEmpty)
diff --git a/packages/DynamicSystemInstallationService/AndroidManifest.xml b/packages/DynamicSystemInstallationService/AndroidManifest.xml
index c2aaeac..776bf2b 100644
--- a/packages/DynamicSystemInstallationService/AndroidManifest.xml
+++ b/packages/DynamicSystemInstallationService/AndroidManifest.xml
@@ -36,6 +36,10 @@
                 <data android:scheme="http" />
                 <data android:scheme="https" />
             </intent-filter>
+            <intent-filter>
+                <action android:name="android.os.image.action.START_INSTALL" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
         </activity>
 
         <receiver
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index 2c4b478..b265a42 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -19,6 +19,7 @@
 import static android.os.AsyncTask.Status.FINISHED;
 import static android.os.AsyncTask.Status.PENDING;
 import static android.os.AsyncTask.Status.RUNNING;
+import static android.os.image.DynamicSystemClient.ACTION_HIDE_NOTIFICATION;
 import static android.os.image.DynamicSystemClient.ACTION_NOTIFY_IF_IN_USE;
 import static android.os.image.DynamicSystemClient.ACTION_START_INSTALL;
 import static android.os.image.DynamicSystemClient.CAUSE_ERROR_EXCEPTION;
@@ -27,6 +28,8 @@
 import static android.os.image.DynamicSystemClient.CAUSE_INSTALL_CANCELLED;
 import static android.os.image.DynamicSystemClient.CAUSE_INSTALL_COMPLETED;
 import static android.os.image.DynamicSystemClient.CAUSE_NOT_SPECIFIED;
+import static android.os.image.DynamicSystemClient.KEY_ENABLE_WHEN_COMPLETED;
+import static android.os.image.DynamicSystemClient.KEY_ONE_SHOT;
 import static android.os.image.DynamicSystemClient.STATUS_IN_PROGRESS;
 import static android.os.image.DynamicSystemClient.STATUS_IN_USE;
 import static android.os.image.DynamicSystemClient.STATUS_NOT_STARTED;
@@ -77,8 +80,6 @@
 
     private static final String TAG = "DynamicSystemInstallationService";
 
-    // TODO (b/131866826): This is currently for test only. Will move this to System API.
-    static final String KEY_ENABLE_WHEN_COMPLETED = "KEY_ENABLE_WHEN_COMPLETED";
     static final String KEY_DSU_SLOT = "KEY_DSU_SLOT";
     static final String DEFAULT_DSU_SLOT = "dsu";
     static final String KEY_PUBKEY = "KEY_PUBKEY";
@@ -172,6 +173,8 @@
 
     // This is for testing only now
     private boolean mEnableWhenCompleted;
+    private boolean mOneShot;
+    private boolean mHideNotification;
 
     private InstallationAsyncTask.Progress mInstallTaskProgress;
     private InstallationAsyncTask mInstallTask;
@@ -229,6 +232,8 @@
             executeRebootToNormalCommand();
         } else if (ACTION_NOTIFY_IF_IN_USE.equals(action)) {
             executeNotifyIfInUseCommand();
+        } else if (ACTION_HIDE_NOTIFICATION.equals(action)) {
+            executeHideNotificationCommand();
         }
 
         return Service.START_NOT_STICKY;
@@ -318,6 +323,7 @@
         long systemSize = intent.getLongExtra(DynamicSystemClient.KEY_SYSTEM_SIZE, 0);
         long userdataSize = intent.getLongExtra(DynamicSystemClient.KEY_USERDATA_SIZE, 0);
         mEnableWhenCompleted = intent.getBooleanExtra(KEY_ENABLE_WHEN_COMPLETED, false);
+        mOneShot = intent.getBooleanExtra(KEY_ONE_SHOT, true);
         String dsuSlot = intent.getStringExtra(KEY_DSU_SLOT);
         String publicKey = intent.getStringExtra(KEY_PUBKEY);
 
@@ -384,9 +390,9 @@
         boolean enabled = false;
 
         if (mInstallTask != null && mInstallTask.isCompleted()) {
-            enabled = mInstallTask.commit();
+            enabled = mInstallTask.commit(mOneShot);
         } else if (isDynamicSystemInstalled()) {
-            enabled = mDynSystem.setEnable(true, true);
+            enabled = mDynSystem.setEnable(true, mOneShot);
         } else {
             Log.e(TAG, "Trying to reboot to AOT while there is no complete installation");
             return;
@@ -439,12 +445,16 @@
     private void executeNotifyIfInUseCommand() {
         switch (getStatus()) {
             case STATUS_IN_USE:
-                startForeground(NOTIFICATION_ID,
-                        buildNotification(STATUS_IN_USE, CAUSE_NOT_SPECIFIED));
+                if (!mHideNotification) {
+                    startForeground(NOTIFICATION_ID,
+                            buildNotification(STATUS_IN_USE, CAUSE_NOT_SPECIFIED));
+                }
                 break;
             case STATUS_READY:
-                startForeground(NOTIFICATION_ID,
-                        buildNotification(STATUS_READY, CAUSE_NOT_SPECIFIED));
+                if (!mHideNotification) {
+                    startForeground(NOTIFICATION_ID,
+                            buildNotification(STATUS_READY, CAUSE_NOT_SPECIFIED));
+                }
                 break;
             case STATUS_IN_PROGRESS:
                 break;
@@ -454,6 +464,16 @@
         }
     }
 
+    private void executeHideNotificationCommand() {
+        mHideNotification = true;
+        switch (getStatus()) {
+            case STATUS_IN_USE:
+            case STATUS_READY:
+                stopForeground(STOP_FOREGROUND_REMOVE);
+                break;
+        }
+    }
+
     private void resetTaskAndStop() {
         resetTaskAndStop(/* removeNotification= */ false);
     }
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
index a41399f..42b620a 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
@@ -803,7 +803,7 @@
         return mIsCompleted;
     }
 
-    boolean commit() {
-        return mDynSystem.setEnable(true, true);
+    boolean commit(boolean oneShot) {
+        return mDynSystem.setEnable(true, oneShot);
     }
 }
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java
index 64e42cc..b522729 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java
@@ -16,6 +16,8 @@
 
 package com.android.dynsystem;
 
+import static android.os.image.DynamicSystemClient.KEY_KEYGUARD_USE_DEFAULT_STRINGS;
+
 import android.app.Activity;
 import android.app.KeyguardManager;
 import android.content.Context;
@@ -47,10 +49,7 @@
         KeyguardManager km = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
 
         if (km != null) {
-            String title = getString(R.string.keyguard_title);
-            String description = getString(R.string.keyguard_description);
-            Intent intent = km.createConfirmDeviceCredentialIntent(title, description);
-
+            Intent intent = createConfirmDeviceCredentialIntent(km);
             if (intent == null) {
                 Log.d(TAG, "This device is not protected by a password/pin");
                 startInstallationService();
@@ -63,6 +62,23 @@
         }
     }
 
+    private Intent createConfirmDeviceCredentialIntent(KeyguardManager km) {
+        final boolean useDefaultStrings =
+                getIntent().getBooleanExtra(KEY_KEYGUARD_USE_DEFAULT_STRINGS, false);
+        final String title;
+        final String description;
+        if (useDefaultStrings) {
+            // Use default strings provided by keyguard manager
+            title = null;
+            description = null;
+        } else {
+            // Use custom strings provided by DSU
+            title = getString(R.string.keyguard_title);
+            description = getString(R.string.keyguard_description);
+        }
+        return km.createConfirmDeviceCredentialIntent(title, description);
+    }
+
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {
diff --git a/packages/PrintSpooler/tests/outofprocess/Android.bp b/packages/PrintSpooler/tests/outofprocess/Android.bp
index 69a1d7f..ef0d122 100644
--- a/packages/PrintSpooler/tests/outofprocess/Android.bp
+++ b/packages/PrintSpooler/tests/outofprocess/Android.bp
@@ -31,7 +31,7 @@
     libs: ["android.test.runner.stubs"],
     static_libs: [
         "androidx.test.rules",
-        "ub-uiautomator",
+        "androidx.test.uiautomator_uiautomator",
         "mockito-target-minus-junit4",
         "print-test-util-lib",
     ],
diff --git a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/WorkflowTest.java b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/WorkflowTest.java
index 132545b..1509b70 100644
--- a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/WorkflowTest.java
+++ b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/WorkflowTest.java
@@ -35,15 +35,15 @@
 import android.print.test.services.FirstPrintService;
 import android.print.test.services.PrinterDiscoverySessionCallbacks;
 import android.print.test.services.StubbablePrinterDiscoverySession;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObject;
-import android.support.test.uiautomator.UiObjectNotFoundException;
-import android.support.test.uiautomator.UiSelector;
-import android.support.test.uiautomator.Until;
 import android.util.Log;
 
 import androidx.test.filters.LargeTest;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject;
+import androidx.test.uiautomator.UiObjectNotFoundException;
+import androidx.test.uiautomator.UiSelector;
+import androidx.test.uiautomator.Until;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
index 8cda376..bf24c86 100644
--- a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
+++ b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
@@ -59,32 +59,36 @@
     public void onBindViewHolder(PreferenceViewHolder holder) {
         super.onBindViewHolder(holder);
         TextView title = holder.itemView.findViewById(android.R.id.title);
-        if (!TextUtils.isEmpty(mContentDescription)) {
+        if (title != null && !TextUtils.isEmpty(mContentDescription)) {
             title.setContentDescription(mContentDescription);
         }
 
         TextView learnMore = holder.itemView.findViewById(R.id.settingslib_learn_more);
-        if (learnMore != null && mLearnMoreListener != null) {
-            learnMore.setVisibility(View.VISIBLE);
-            if (TextUtils.isEmpty(mLearnMoreText)) {
-                mLearnMoreText = learnMore.getText();
+        if (learnMore != null) {
+            if (mLearnMoreListener != null) {
+                learnMore.setVisibility(View.VISIBLE);
+                if (TextUtils.isEmpty(mLearnMoreText)) {
+                    mLearnMoreText = learnMore.getText();
+                } else {
+                    learnMore.setText(mLearnMoreText);
+                }
+                SpannableString learnMoreText = new SpannableString(mLearnMoreText);
+                if (mLearnMoreSpan != null) {
+                    learnMoreText.removeSpan(mLearnMoreSpan);
+                }
+                mLearnMoreSpan = new FooterLearnMoreSpan(mLearnMoreListener);
+                learnMoreText.setSpan(mLearnMoreSpan, 0,
+                        learnMoreText.length(), 0);
+                learnMore.setText(learnMoreText);
             } else {
-                learnMore.setText(mLearnMoreText);
+                learnMore.setVisibility(View.GONE);
             }
-            SpannableString learnMoreText = new SpannableString(mLearnMoreText);
-            if (mLearnMoreSpan != null) {
-                learnMoreText.removeSpan(mLearnMoreSpan);
-            }
-            mLearnMoreSpan = new FooterLearnMoreSpan(mLearnMoreListener);
-            learnMoreText.setSpan(mLearnMoreSpan, 0,
-                    learnMoreText.length(), 0);
-            learnMore.setText(learnMoreText);
-        } else {
-            learnMore.setVisibility(View.GONE);
         }
 
         View icon = holder.itemView.findViewById(R.id.icon_frame);
-        icon.setVisibility(mIconVisibility);
+        if (icon != null) {
+            icon.setVisibility(mIconVisibility);
+        }
     }
 
     @Override
diff --git a/packages/SettingsLib/OWNERS b/packages/SettingsLib/OWNERS
index 1b3020a..81340f5 100644
--- a/packages/SettingsLib/OWNERS
+++ b/packages/SettingsLib/OWNERS
@@ -3,9 +3,8 @@
 edgarwang@google.com
 evanlaird@google.com
 juliacr@google.com
-lijun@google.com
-songchenxi@google.com
 yantingyang@google.com
+ykhung@google.com
 
 # Exempt resource files (because they are in a flat directory and too hard to manage via OWNERS)
 per-file *.xml=*
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt
index 30a4349..6f2c38c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt
@@ -18,11 +18,11 @@
 
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Tab
 import androidx.compose.material3.Text
+import androidx.compose.material3.minimumInteractiveComponentSize
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
@@ -46,7 +46,7 @@
         selected = selected,
         onClick = onClick,
         modifier = Modifier
-            .height(48.dp)
+            .minimumInteractiveComponentSize()
             .padding(horizontal = 4.dp, vertical = 6.dp)
             .clip(SettingsShape.CornerMedium)
             .background(
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
index 5342def..2c3e58e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
@@ -20,9 +20,12 @@
 import android.content.Intent
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ApplicationInfoFlags
 import android.content.pm.ResolveInfo
 import com.android.internal.R
+import com.android.settingslib.spaprivileged.framework.common.userManager
 import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
@@ -33,7 +36,11 @@
  */
 internal interface AppListRepository {
     /** Loads the list of [ApplicationInfo]. */
-    suspend fun loadApps(userId: Int, showInstantApps: Boolean): List<ApplicationInfo>
+    suspend fun loadApps(
+        userId: Int,
+        showInstantApps: Boolean = false,
+        matchAnyUserForAdmin: Boolean = false,
+    ): List<ApplicationInfo>
 
     /** Gets the flow of predicate that could used to filter system app. */
     fun showSystemPredicate(
@@ -61,10 +68,12 @@
 
 internal class AppListRepositoryImpl(private val context: Context) : AppListRepository {
     private val packageManager = context.packageManager
+    private val userManager = context.userManager
 
     override suspend fun loadApps(
         userId: Int,
         showInstantApps: Boolean,
+        matchAnyUserForAdmin: Boolean,
     ): List<ApplicationInfo> = coroutineScope {
         val hiddenSystemModulesDeferred = async {
             packageManager.getInstalledModules(0)
@@ -75,12 +84,8 @@
         val hideWhenDisabledPackagesDeferred = async {
             context.resources.getStringArray(R.array.config_hideWhenDisabled_packageNames)
         }
-        val flags = PackageManager.ApplicationInfoFlags.of(
-            (PackageManager.MATCH_DISABLED_COMPONENTS or
-                PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong()
-        )
         val installedApplicationsAsUser =
-            packageManager.getInstalledApplicationsAsUser(flags, userId)
+            getInstalledApplications(userId, matchAnyUserForAdmin)
 
         val hiddenSystemModules = hiddenSystemModulesDeferred.await()
         val hideWhenDisabledPackages = hideWhenDisabledPackagesDeferred.await()
@@ -89,6 +94,46 @@
         }
     }
 
+    private suspend fun getInstalledApplications(
+        userId: Int,
+        matchAnyUserForAdmin: Boolean,
+    ): List<ApplicationInfo> {
+        val regularFlags = ApplicationInfoFlags.of(
+            (PackageManager.MATCH_DISABLED_COMPONENTS or
+                PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong()
+        )
+        return if (!matchAnyUserForAdmin || !userManager.getUserInfo(userId).isAdmin) {
+            packageManager.getInstalledApplicationsAsUser(regularFlags, userId)
+        } else {
+            coroutineScope {
+                val deferredPackageNamesInChildProfiles =
+                    userManager.getProfileIdsWithDisabled(userId)
+                        .filter { it != userId }
+                        .map {
+                            async {
+                                packageManager.getInstalledApplicationsAsUser(regularFlags, it)
+                                    .map { it.packageName }
+                            }
+                        }
+                val adminFlags = ApplicationInfoFlags.of(
+                    PackageManager.MATCH_ANY_USER.toLong() or regularFlags.value
+                )
+                val allInstalledApplications =
+                    packageManager.getInstalledApplicationsAsUser(adminFlags, userId)
+                val packageNamesInChildProfiles = deferredPackageNamesInChildProfiles
+                    .awaitAll()
+                    .flatten()
+                    .toSet()
+                // If an app is for a child profile and not installed on the owner, not display as
+                // 'not installed for this user' in the owner. This will prevent duplicates of work
+                // only apps showing up in the personal profile.
+                allInstalledApplications.filter {
+                    it.installed || it.packageName !in packageNamesInChildProfiles
+                }
+            }
+        }
+    }
+
     override fun showSystemPredicate(
         userIdFlow: Flow<Int>,
         showSystemFlow: Flow<Boolean>,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
index 8896042..bd99ebd 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
@@ -80,12 +80,15 @@
     private val scope = viewModelScope + Dispatchers.IO
 
     private val userSubGraphsFlow = appListConfig.flow.map { config ->
-        config.userIds.map { userId -> UserSubGraph(userId, config.showInstantApps) }
+        config.userIds.map { userId ->
+            UserSubGraph(userId, config.showInstantApps, config.matchAnyUserForAdmin)
+        }
     }.shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1)
 
     private inner class UserSubGraph(
         private val userId: Int,
         private val showInstantApps: Boolean,
+        private val matchAnyUserForAdmin: Boolean,
     ) {
         private val userIdFlow = flowOf(userId)
 
@@ -110,7 +113,8 @@
 
         fun reloadApps() {
             scope.launch {
-                appsStateFlow.value = appListRepository.loadApps(userId, showInstantApps)
+                appsStateFlow.value =
+                    appListRepository.loadApps(userId, showInstantApps, matchAnyUserForAdmin)
             }
         }
     }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index f4a6b59..066db34 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -62,6 +62,7 @@
 data class AppListConfig(
     val userIds: List<Int>,
     val showInstantApps: Boolean,
+    val matchAnyUserForAdmin: Boolean,
 )
 
 data class AppListState(
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
index 2ebbe8a..89bfa0e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
@@ -38,6 +38,7 @@
     title: String,
     listModel: AppListModel<T>,
     showInstantApps: Boolean = false,
+    matchAnyUserForAdmin: Boolean = false,
     primaryUserOnly: Boolean = false,
     noItemMessage: String? = null,
     moreOptions: @Composable MoreOptionsScope.() -> Unit = {},
@@ -59,6 +60,7 @@
                 config = AppListConfig(
                     userIds = userGroup.userInfos.map { it.id },
                     showInstantApps = showInstantApps,
+                    matchAnyUserForAdmin = matchAnyUserForAdmin,
                 ),
                 listModel = listModel,
                 state = AppListState(
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
index 57972ed..b732a6a 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
@@ -23,9 +23,13 @@
 import android.content.pm.PackageManager.ApplicationInfoFlags
 import android.content.pm.PackageManager.ResolveInfoFlags
 import android.content.pm.ResolveInfo
+import android.content.pm.UserInfo
 import android.content.res.Resources
+import android.os.UserManager
+import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.internal.R
+import com.android.settingslib.spaprivileged.framework.common.userManager
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.first
@@ -35,10 +39,13 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
 import org.mockito.Mock
 import org.mockito.Mockito.any
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.eq
+import org.mockito.Mockito.verify
+import org.mockito.Spy
 import org.mockito.junit.MockitoJUnit
 import org.mockito.junit.MockitoRule
 import org.mockito.Mockito.`when` as whenever
@@ -49,8 +56,8 @@
     @get:Rule
     val mockito: MockitoRule = MockitoJUnit.rule()
 
-    @Mock
-    private lateinit var context: Context
+    @Spy
+    private val context: Context = ApplicationProvider.getApplicationContext()
 
     @Mock
     private lateinit var resources: Resources
@@ -58,6 +65,9 @@
     @Mock
     private lateinit var packageManager: PackageManager
 
+    @Mock
+    private lateinit var userManager: UserManager
+
     private lateinit var repository: AppListRepository
 
     @Before
@@ -66,36 +76,116 @@
         whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
             .thenReturn(emptyArray())
         whenever(context.packageManager).thenReturn(packageManager)
+        whenever(context.userManager).thenReturn(userManager)
         whenever(packageManager.getInstalledModules(anyInt())).thenReturn(emptyList())
         whenever(
-            packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), eq(USER_ID))
+            packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), anyInt())
         ).thenReturn(emptyList())
+        whenever(userManager.getUserInfo(ADMIN_USER_ID)).thenReturn(UserInfo().apply {
+            flags = UserInfo.FLAG_ADMIN
+        })
+        whenever(userManager.getProfileIdsWithDisabled(ADMIN_USER_ID))
+            .thenReturn(intArrayOf(ADMIN_USER_ID, MANAGED_PROFILE_USER_ID))
 
         repository = AppListRepositoryImpl(context)
     }
 
-    private fun mockInstalledApplications(apps: List<ApplicationInfo>) {
+    private fun mockInstalledApplications(apps: List<ApplicationInfo>, userId: Int) {
         whenever(
-            packageManager.getInstalledApplicationsAsUser(any<ApplicationInfoFlags>(), eq(USER_ID))
+            packageManager.getInstalledApplicationsAsUser(any<ApplicationInfoFlags>(), eq(userId))
         ).thenReturn(apps)
     }
 
     @Test
     fun loadApps_notShowInstantApps() = runTest {
-        mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP))
+        mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP), ADMIN_USER_ID)
 
-        val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
+        val appList = repository.loadApps(
+            userId = ADMIN_USER_ID,
+            showInstantApps = false,
+        )
 
-        assertThat(appListFlow).containsExactly(NORMAL_APP)
+        assertThat(appList).containsExactly(NORMAL_APP)
     }
 
     @Test
     fun loadApps_showInstantApps() = runTest {
-        mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP))
+        mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP), ADMIN_USER_ID)
 
-        val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = true)
+        val appList = repository.loadApps(
+            userId = ADMIN_USER_ID,
+            showInstantApps = true,
+        )
 
-        assertThat(appListFlow).containsExactly(NORMAL_APP, INSTANT_APP)
+        assertThat(appList).containsExactly(NORMAL_APP, INSTANT_APP)
+    }
+
+    @Test
+    fun loadApps_notMatchAnyUserForAdmin_withRegularFlags() = runTest {
+        mockInstalledApplications(listOf(NORMAL_APP), ADMIN_USER_ID)
+
+        val appList = repository.loadApps(
+            userId = ADMIN_USER_ID,
+            matchAnyUserForAdmin = false,
+        )
+
+        assertThat(appList).containsExactly(NORMAL_APP)
+        val flags = ArgumentCaptor.forClass(ApplicationInfoFlags::class.java)
+        verify(packageManager).getInstalledApplicationsAsUser(flags.capture(), eq(ADMIN_USER_ID))
+        assertThat(flags.value.value).isEqualTo(
+            PackageManager.MATCH_DISABLED_COMPONENTS or
+                PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+        )
+    }
+
+    @Test
+    fun loadApps_matchAnyUserForAdmin_withMatchAnyUserFlag() = runTest {
+        mockInstalledApplications(listOf(NORMAL_APP), ADMIN_USER_ID)
+
+        val appList = repository.loadApps(
+            userId = ADMIN_USER_ID,
+            matchAnyUserForAdmin = true,
+        )
+
+        assertThat(appList).containsExactly(NORMAL_APP)
+        val flags = ArgumentCaptor.forClass(ApplicationInfoFlags::class.java)
+        verify(packageManager).getInstalledApplicationsAsUser(flags.capture(), eq(ADMIN_USER_ID))
+        assertThat(flags.value.value and PackageManager.MATCH_ANY_USER.toLong()).isGreaterThan(0L)
+    }
+
+    @Test
+    fun loadApps_matchAnyUserForAdminAndInstalledOnManagedProfileOnly_notDisplayed() = runTest {
+        val managedProfileOnlyPackageName = "installed.on.managed.profile.only"
+        mockInstalledApplications(listOf(ApplicationInfo().apply {
+            packageName = managedProfileOnlyPackageName
+        }), ADMIN_USER_ID)
+        mockInstalledApplications(listOf(ApplicationInfo().apply {
+            packageName = managedProfileOnlyPackageName
+            flags = ApplicationInfo.FLAG_INSTALLED
+        }), MANAGED_PROFILE_USER_ID)
+
+        val appList = repository.loadApps(
+            userId = ADMIN_USER_ID,
+            matchAnyUserForAdmin = true,
+        )
+
+        assertThat(appList).isEmpty()
+    }
+
+    @Test
+    fun loadApps_matchAnyUserForAdminAndInstalledOnSecondaryUserOnly_displayed() = runTest {
+        val secondaryUserOnlyApp = ApplicationInfo().apply {
+            packageName = "installed.on.secondary.user.only"
+        }
+        mockInstalledApplications(listOf(secondaryUserOnlyApp), ADMIN_USER_ID)
+        mockInstalledApplications(emptyList(), MANAGED_PROFILE_USER_ID)
+
+        val appList = repository.loadApps(
+            userId = ADMIN_USER_ID,
+            matchAnyUserForAdmin = true,
+        )
+
+        assertThat(appList).containsExactly(secondaryUserOnlyApp)
     }
 
     @Test
@@ -106,11 +196,11 @@
         }
         whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
             .thenReturn(arrayOf(app.packageName))
-        mockInstalledApplications(listOf(app))
+        mockInstalledApplications(listOf(app), ADMIN_USER_ID)
 
-        val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
+        val appList = repository.loadApps(userId = ADMIN_USER_ID)
 
-        assertThat(appListFlow).isEmpty()
+        assertThat(appList).isEmpty()
     }
 
     @Test
@@ -122,11 +212,11 @@
         }
         whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
             .thenReturn(arrayOf(app.packageName))
-        mockInstalledApplications(listOf(app))
+        mockInstalledApplications(listOf(app), ADMIN_USER_ID)
 
-        val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
+        val appList = repository.loadApps(userId = ADMIN_USER_ID)
 
-        assertThat(appListFlow).isEmpty()
+        assertThat(appList).isEmpty()
     }
 
     @Test
@@ -137,11 +227,11 @@
         }
         whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
             .thenReturn(arrayOf(app.packageName))
-        mockInstalledApplications(listOf(app))
+        mockInstalledApplications(listOf(app), ADMIN_USER_ID)
 
-        val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
+        val appList = repository.loadApps(userId = ADMIN_USER_ID)
 
-        assertThat(appListFlow).containsExactly(app)
+        assertThat(appList).containsExactly(app)
     }
 
     @Test
@@ -151,11 +241,11 @@
             enabled = false
             enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER
         }
-        mockInstalledApplications(listOf(app))
+        mockInstalledApplications(listOf(app), ADMIN_USER_ID)
 
-        val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
+        val appList = repository.loadApps(userId = ADMIN_USER_ID)
 
-        assertThat(appListFlow).containsExactly(app)
+        assertThat(appList).containsExactly(app)
     }
 
     @Test
@@ -164,11 +254,11 @@
             packageName = "disabled"
             enabled = false
         }
-        mockInstalledApplications(listOf(app))
+        mockInstalledApplications(listOf(app), ADMIN_USER_ID)
 
-        val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
+        val appList = repository.loadApps(userId = ADMIN_USER_ID)
 
-        assertThat(appListFlow).isEmpty()
+        assertThat(appList).isEmpty()
     }
 
     @Test
@@ -219,7 +309,11 @@
         val app = IN_LAUNCHER_APP
 
         whenever(
-            packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), eq(USER_ID))
+            packageManager.queryIntentActivitiesAsUser(
+                any(),
+                any<ResolveInfoFlags>(),
+                eq(ADMIN_USER_ID)
+            )
         ).thenReturn(listOf(resolveInfoOf(packageName = app.packageName)))
 
         val showSystemPredicate = getShowSystemPredicate(showSystem = false)
@@ -229,12 +323,16 @@
 
     @Test
     fun getSystemPackageNames_returnExpectedValues() = runTest {
-        mockInstalledApplications(listOf(
-                NORMAL_APP, INSTANT_APP, SYSTEM_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUNCHER_APP))
+        mockInstalledApplications(
+            apps = listOf(
+                NORMAL_APP, INSTANT_APP, SYSTEM_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUNCHER_APP
+            ),
+            userId = ADMIN_USER_ID,
+        )
 
         val systemPackageNames = AppListRepositoryUtil.getSystemPackageNames(
             context = context,
-            userId = USER_ID,
+            userId = ADMIN_USER_ID,
             showInstantApps = false,
         )
 
@@ -243,12 +341,13 @@
 
     private suspend fun getShowSystemPredicate(showSystem: Boolean) =
         repository.showSystemPredicate(
-            userIdFlow = flowOf(USER_ID),
+            userIdFlow = flowOf(ADMIN_USER_ID),
             showSystemFlow = flowOf(showSystem),
         ).first()
 
     private companion object {
-        const val USER_ID = 0
+        const val ADMIN_USER_ID = 0
+        const val MANAGED_PROFILE_USER_ID = 11
 
         val NORMAL_APP = ApplicationInfo().apply {
             packageName = "normal"
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
index 4f0cdde..36d9db5 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
@@ -85,7 +85,11 @@
     }
 
     private object FakeAppListRepository : AppListRepository {
-        override suspend fun loadApps(userId: Int, showInstantApps: Boolean) = listOf(APP)
+        override suspend fun loadApps(
+            userId: Int,
+            showInstantApps: Boolean,
+            matchAnyUserForAdmin: Boolean,
+        ) = listOf(APP)
 
         override fun showSystemPredicate(
             userIdFlow: Flow<Int>,
@@ -112,7 +116,11 @@
         const val USER_ID = 0
         const val PACKAGE_NAME = "package.name"
         const val LABEL = "Label"
-        val CONFIG = AppListConfig(userIds = listOf(USER_ID), showInstantApps = false)
+        val CONFIG = AppListConfig(
+            userIds = listOf(USER_ID),
+            showInstantApps = false,
+            matchAnyUserForAdmin = false,
+        )
         val APP = ApplicationInfo().apply {
             packageName = PACKAGE_NAME
         }
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
index a99d02d..241a134 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
@@ -114,7 +114,11 @@
     ) {
         composeTestRule.setContent {
             AppListInput(
-                config = AppListConfig(userIds = listOf(USER_ID), showInstantApps = false),
+                config = AppListConfig(
+                    userIds = listOf(USER_ID),
+                    showInstantApps = false,
+                    matchAnyUserForAdmin = false,
+                ),
                 listModel = TestAppListModel(enableGrouping = enableGrouping),
                 state = AppListState(
                     showSystem = false.toState(),
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
index e22f3f0..5fbb4c3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
@@ -37,8 +37,6 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -287,16 +285,7 @@
             return defaultValue;
         }
 
-        try {
-            Method method = mService.getClass().getDeclaredMethod("getDeviceSideInternal",
-                    BluetoothDevice.class);
-            method.setAccessible(true);
-            return (int) method.invoke(mService, device);
-        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
-            Log.e(TAG, "fail to get getDeviceSideInternal\n" + e.toString() + "\n"
-                    + Log.getStackTraceString(new Throwable()));
-            return defaultValue;
-        }
+        return mService.getDeviceSide(device);
     }
 
     /**
@@ -313,17 +302,7 @@
             return defaultValue;
         }
 
-        try {
-            Method method = mService.getClass().getDeclaredMethod("getDeviceModeInternal",
-                    BluetoothDevice.class);
-            method.setAccessible(true);
-            return (int) method.invoke(mService, device);
-        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
-            Log.e(TAG, "fail to get getDeviceModeInternal\n" + e.toString() + "\n"
-                    + Log.getStackTraceString(new Throwable()));
-
-            return defaultValue;
-        }
+        return mService.getDeviceMode(device);
     }
 
     public String toString() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
index a3db6d7..e28ada4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
@@ -120,9 +120,9 @@
      * @return true if the request succeeded.
      */
     public static synchronized boolean setPowerSaveMode(Context context,
-            boolean enable, boolean needFirstTimeWarning) {
+            boolean enable, boolean needFirstTimeWarning, @SaverManualEnabledReason int reason) {
         if (DEBUG) {
-            Log.d(TAG, "Battery saver turning " + (enable ? "ON" : "OFF"));
+            Log.d(TAG, "Battery saver turning " + (enable ? "ON" : "OFF") + ", reason: " + reason);
         }
         final ContentResolver cr = context.getContentResolver();
 
@@ -152,6 +152,7 @@
                     sendSystemUiBroadcast(context, ACTION_SHOW_AUTO_SAVER_SUGGESTION,
                             confirmationExtras);
                 }
+                recordBatterySaverEnabledReason(context, reason);
             }
 
             return true;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
index ad022a6..cb386fb 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
@@ -16,6 +16,7 @@
 
 package com.android.settingslib.fuelgauge;
 
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_UNKNOWN;
 import static com.android.settingslib.fuelgauge.BatterySaverUtils.KEY_NO_SCHEDULE;
 import static com.android.settingslib.fuelgauge.BatterySaverUtils.KEY_PERCENTAGE;
 
@@ -72,7 +73,8 @@
         Secure.putString(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, "null");
         Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
 
-        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)).isFalse();
+        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true,
+                SAVER_ENABLED_UNKNOWN)).isFalse();
 
         verify(mMockContext, times(1)).sendBroadcast(any(Intent.class));
         verify(mMockPowerManager, times(0)).setPowerSaveModeEnabled(anyBoolean());
@@ -92,7 +94,8 @@
         Secure.putInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1);
         Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
 
-        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)).isTrue();
+        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true,
+                SAVER_ENABLED_UNKNOWN)).isTrue();
 
         verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
         verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true));
@@ -111,7 +114,8 @@
         Secure.putInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1);
         Secure.putInt(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, 1);
 
-        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)).isTrue();
+        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true,
+                SAVER_ENABLED_UNKNOWN)).isTrue();
 
         verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
         verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true));
@@ -129,7 +133,8 @@
         Secure.putString(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, "null");
         Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
 
-        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, false)).isTrue();
+        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, false,
+                SAVER_ENABLED_UNKNOWN)).isTrue();
 
         verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
         verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true));
@@ -147,7 +152,8 @@
         Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
 
         // When disabling, needFirstTimeWarning doesn't matter.
-        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, false)).isTrue();
+        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, false,
+                SAVER_ENABLED_UNKNOWN)).isTrue();
 
         verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
         verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(false));
@@ -166,7 +172,8 @@
         Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
 
         // When disabling, needFirstTimeWarning doesn't matter.
-        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, true)).isTrue();
+        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, true,
+                SAVER_ENABLED_UNKNOWN)).isTrue();
 
         verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
         verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(false));
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
index 55125c5..049c90e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
@@ -18,6 +18,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
 import android.content.Context;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -87,4 +90,52 @@
 
         assertThat(mFooterPreference.mIconVisibility).isEqualTo(View.GONE);
     }
+
+    @Test
+    public void onBindViewHolder_whenTitleIsNull_shouldNotRaiseNpe() {
+        PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests(
+                LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null)));
+        when(viewHolder.findViewById(R.id.title)).thenReturn(null);
+
+        Throwable actualThrowable = null;
+        try {
+            mFooterPreference.onBindViewHolder(viewHolder);
+        } catch (Throwable throwable) {
+            actualThrowable = throwable;
+        }
+
+        assertThat(actualThrowable).isNull();
+    }
+
+    @Test
+    public void onBindViewHolder_whenLearnMoreIsNull_shouldNotRaiseNpe() {
+        PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests(
+                LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null)));
+        when(viewHolder.findViewById(R.id.settingslib_learn_more)).thenReturn(null);
+
+        Throwable actualThrowable = null;
+        try {
+            mFooterPreference.onBindViewHolder(viewHolder);
+        } catch (Throwable throwable) {
+            actualThrowable = throwable;
+        }
+
+        assertThat(actualThrowable).isNull();
+    }
+
+    @Test
+    public void onBindViewHolder_whenIconFrameIsNull_shouldNotRaiseNpe() {
+        PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests(
+                LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null)));
+        when(viewHolder.findViewById(R.id.icon_frame)).thenReturn(null);
+
+        Throwable actualThrowable = null;
+        try {
+            mFooterPreference.onBindViewHolder(viewHolder);
+        } catch (Throwable throwable) {
+            actualThrowable = throwable;
+        }
+
+        assertThat(actualThrowable).isNull();
+    }
 }
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index fedfb43..78d93bd 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -58,7 +58,6 @@
     <uses-permission android:name="android.permission.ACCEPT_HANDOVER" />
     <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
     <uses-permission android:name="android.permission.BODY_SENSORS" />
-    <uses-permission android:name="android.permission.BODY_SENSORS_WRIST_TEMPERATURE" />
     <uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" />
     <uses-permission android:name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" />
     <uses-permission android:name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" />
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index dabb578..a00f401 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -984,7 +984,13 @@
             android:exported="true"
             android:excludeFromRecents="true"
             android:resizeableActivity="false"
-            android:theme="@android:style/Theme.NoDisplay" />
+            android:theme="@android:style/Theme.NoDisplay" >
+
+            <intent-filter>
+                <action android:name="com.android.systemui.action.LAUNCH_NOTE_TASK"/>
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
 
         <!-- LaunchNoteTaskManagedProfileProxyActivity MUST NOT be exported because it allows caller
              to specify an Android user when launching the default notes app. -->
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index a7e95b5..eaf3229 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.plugins.PluginListener
 import com.android.systemui.plugins.PluginManager
 import com.android.systemui.util.Assert
+import java.io.PrintWriter
 import java.util.concurrent.ConcurrentHashMap
 import java.util.concurrent.atomic.AtomicBoolean
 import kotlinx.coroutines.CoroutineDispatcher
@@ -485,6 +486,14 @@
         return availableClocks[targetClockId]?.provider?.createClock(settings)
     }
 
+    fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.println("ClockRegistry:")
+        pw.println("  settings = $settings")
+        for ((id, info) in availableClocks) {
+            pw.println("  availableClocks[$id] = $info")
+        }
+    }
+
     private data class ClockInfo(
         val metadata: ClockMetadata,
         var provider: ClockProvider?,
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index 322fc77..05630e7 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -199,6 +199,9 @@
      */
     val hasCustomPositionUpdatedAnimation: Boolean = false,
 
+    /** Transition to AOD should move smartspace like large clock instead of small clock */
+    val useAlternateSmartspaceAODTransition: Boolean = false,
+
     /** True if the clock will react to tone changes in the seed color. */
     val isReactiveToTone: Boolean = true,
 )
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
index f71c137..2dd146c5 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
@@ -47,6 +47,7 @@
             }
     }
 
+    // Values for WeatherStateIcon must stay in sync with go/g3-WeatherStateIcon
     enum class WeatherStateIcon(val id: Int) {
         UNKNOWN_ICON(0),
 
diff --git a/packages/SystemUI/res-keyguard/drawable/fp_to_locked.xml b/packages/SystemUI/res-keyguard/drawable/fp_to_locked.xml
new file mode 100644
index 0000000..61a1cb5
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/fp_to_locked.xml
@@ -0,0 +1,165 @@
+<?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
+  -->
+<animated-vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <aapt:attr name="android:drawable">
+        <vector android:height="65dp" android:width="46dp" android:viewportHeight="65" android:viewportWidth="46">
+            <group android:name="_R_G">
+                <group android:name="_R_G_L_1_G" android:translateX="3.75" android:translateY="8.25">
+                    <path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/>
+                    <path android:name="_R_G_L_1_G_D_1_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/>
+                    <path android:name="_R_G_L_1_G_D_2_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/>
+                    <path android:name="_R_G_L_1_G_D_3_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/>
+                </group>
+                <group android:name="_R_G_L_0_G" android:translateX="20.357" android:translateY="35.75" android:pivotX="2.75" android:pivotY="2.75" android:scaleX="1.41866" android:scaleY="1.41866">
+                    <path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#FF000000" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c "/>
+                </group>
+            </group>
+            <group android:name="time_group"/>
+        </vector>
+    </aapt:attr>
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 " android:valueTo="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="pathData" android:duration="143" android:startOffset="107" android:valueFrom="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueTo="M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.331,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G_D_1_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="strokeAlpha" android:duration="140" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="strokeAlpha" android:duration="50" android:startOffset="140" android:valueFrom="1" android:valueTo="0" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G_D_1_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 " android:valueTo="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="107" android:valueFrom="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueTo="M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G_D_2_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="pathData" android:duration="250" android:startOffset="0" android:valueFrom="M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 " android:valueTo="M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.189,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G_D_3_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="pathData" android:duration="95" android:startOffset="0" android:valueFrom="M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 " android:valueTo="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="pathData" android:duration="24" android:startOffset="95" android:valueFrom="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueTo="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.833,0.767 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="pathData" android:duration="81" android:startOffset="119" android:valueFrom="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueTo="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.261,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="fillAlpha" android:duration="120" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="fillAlpha" android:duration="20" android:startOffset="120" android:valueFrom="0" android:valueTo="1" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="scaleX" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="scaleY" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="scaleX" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="scaleY" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="translateX" android:duration="517" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
index 951d6fe..f3325ec 100644
--- a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
+++ b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
@@ -104,4 +104,9 @@
         android:fromId="@id/unlocked"
         android:toId="@id/locked"
         android:drawable="@drawable/unlocked_to_locked" />
+
+    <transition
+        android:fromId="@id/locked_fp"
+        android:toId="@id/locked"
+        android:drawable="@drawable/fp_to_locked" />
 </animated-selector>
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index 2143fc4..edd3047 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -21,19 +21,19 @@
     <!-- Instructions telling the user to enter their PIN password to unlock the keyguard [CHAR LIMIT=30] -->
     <string name="keyguard_enter_your_pin">Enter your PIN</string>
 
-    <!-- Instructions telling the user to enter their PIN password to unlock the keyguard [CHAR LIMIT=26] -->
+    <!-- Instructions telling the user to enter their PIN password to unlock the keyguard [CHAR LIMIT=48] -->
     <string name="keyguard_enter_pin">Enter PIN</string>
 
     <!-- Instructions telling the user to enter their pattern to unlock the keyguard [CHAR LIMIT=30] -->
     <string name="keyguard_enter_your_pattern">Enter your pattern</string>
 
-    <!-- Instructions telling the user to enter their pattern to unlock the keyguard [CHAR LIMIT=26] -->
+    <!-- Instructions telling the user to enter their pattern to unlock the keyguard [CHAR LIMIT=48] -->
     <string name="keyguard_enter_pattern">Draw pattern</string>
 
     <!-- Instructions telling the user to enter their text password to unlock the keyguard [CHAR LIMIT=30] -->
     <string name="keyguard_enter_your_password">Enter your password</string>
 
-    <!-- Instructions telling the user to enter their text password to unlock the keyguard [CHAR LIMIT=26] -->
+    <!-- Instructions telling the user to enter their text password to unlock the keyguard [CHAR LIMIT=48] -->
     <string name="keyguard_enter_password">Enter password</string>
 
     <!-- Shown in the lock screen when there is SIM card IO error. -->
@@ -128,103 +128,103 @@
     <!-- Message shown when user enters wrong pattern -->
     <string name="kg_wrong_pattern">Wrong pattern</string>
 
-    <!-- Message shown when user enters wrong pattern [CHAR LIMIT=26] -->
+    <!-- Message shown when user enters wrong pattern [CHAR LIMIT=48] -->
     <string name="kg_wrong_pattern_try_again">Wrong pattern. Try again.</string>
 
     <!-- Message shown when user enters wrong password -->
     <string name="kg_wrong_password">Wrong password</string>
 
-    <!-- Message shown when user enters wrong pattern [CHAR LIMIT=26] -->
+    <!-- Message shown when user enters wrong pattern [CHAR LIMIT=48] -->
     <string name="kg_wrong_password_try_again">Wrong password. Try again.</string>
 
     <!-- Message shown when user enters wrong PIN -->
     <string name="kg_wrong_pin">Wrong PIN</string>
 
-    <!-- Message shown when user enters wrong PIN  [CHAR LIMIT=26] -->
+    <!-- Message shown when user enters wrong PIN  [CHAR LIMIT=48] -->
     <string name="kg_wrong_pin_try_again">Wrong PIN. Try again.</string>
 
-    <!-- Message shown when user enters wrong PIN/password/pattern below the main message, for ex: "Wrong PIN. Try again" in line 1 and the following text in line 2.  [CHAR LIMIT=52] -->
+    <!-- Message shown when user enters wrong PIN/password/pattern below the main message, for ex: "Wrong PIN. Try again" in line 1 and the following text in line 2.  [CHAR LIMIT=70] -->
     <string name="kg_wrong_input_try_fp_suggestion">Or unlock with fingerprint</string>
 
-    <!-- Message shown when user fingerprint is not recognized  [CHAR LIMIT=26] -->
+    <!-- Message shown when user fingerprint is not recognized  [CHAR LIMIT=48] -->
     <string name="kg_fp_not_recognized">Fingerprint not recognized</string>
 
-    <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password  [CHAR LIMIT=26] -->
+    <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password  [CHAR LIMIT=48] -->
     <string name="bouncer_face_not_recognized">Face not recognized</string>
 
-    <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password  [CHAR LIMIT=52] -->
+    <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password  [CHAR LIMIT=70] -->
     <string name="kg_bio_try_again_or_pin">Try again or enter PIN</string>
 
-    <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password  [CHAR LIMIT=52] -->
+    <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password  [CHAR LIMIT=70] -->
     <string name="kg_bio_try_again_or_password">Try again or enter password</string>
 
-    <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password  [CHAR LIMIT=52] -->
+    <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password  [CHAR LIMIT=70] -->
     <string name="kg_bio_try_again_or_pattern">Try again or draw pattern</string>
 
-    <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint  [CHAR LIMIT=52] -->
+    <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint  [CHAR LIMIT=70] -->
     <string name="kg_bio_too_many_attempts_pin">PIN is required after too many attempts</string>
 
-    <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint  [CHAR LIMIT=52] -->
+    <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint  [CHAR LIMIT=70] -->
     <string name="kg_bio_too_many_attempts_password">Password is required after too many attempts</string>
 
-    <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint  [CHAR LIMIT=52] -->
+    <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint  [CHAR LIMIT=70] -->
     <string name="kg_bio_too_many_attempts_pattern">Pattern is required after too many attempts</string>
 
-    <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=26]  -->
+    <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=48]  -->
     <string name="kg_unlock_with_pin_or_fp">Unlock with PIN or fingerprint</string>
 
-    <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=26]  -->
+    <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=48]  -->
     <string name="kg_unlock_with_password_or_fp">Unlock with password or fingerprint</string>
 
-    <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=26]  -->
+    <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=48]  -->
     <string name="kg_unlock_with_pattern_or_fp">Unlock with pattern or fingerprint</string>
 
-    <!-- Message shown when we are on bouncer after Device admin requested lockdown.  [CHAR LIMIT=52] -->
+    <!-- Message shown when we are on bouncer after Device admin requested lockdown.  [CHAR LIMIT=70] -->
     <string name="kg_prompt_after_dpm_lock">For added security, device was locked by work policy</string>
 
-    <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown.  [CHAR LIMIT=52] -->
+    <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown.  [CHAR LIMIT=70] -->
     <string name="kg_prompt_after_user_lockdown_pin">PIN is required after lockdown</string>
 
-    <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown.  [CHAR LIMIT=52] -->
+    <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown.  [CHAR LIMIT=70] -->
     <string name="kg_prompt_after_user_lockdown_password">Password is required after lockdown</string>
 
-    <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown.  [CHAR LIMIT=52] -->
+    <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown.  [CHAR LIMIT=70] -->
     <string name="kg_prompt_after_user_lockdown_pattern">Pattern is required after lockdown</string>
 
-    <!-- Message shown to prepare for an unattended update (OTA). Also known as an over-the-air (OTA) update.  [CHAR LIMIT=52] -->
+    <!-- Message shown to prepare for an unattended update (OTA). Also known as an over-the-air (OTA) update.  [CHAR LIMIT=70] -->
     <string name="kg_prompt_unattended_update">Update will install during inactive hours</string>
 
-    <!-- Message shown when primary authentication hasn't been used for some time.  [CHAR LIMIT=52] -->
+    <!-- Message shown when primary authentication hasn't been used for some time.  [CHAR LIMIT=70] -->
     <string name="kg_prompt_pin_auth_timeout">Added security required. PIN not used for a while.</string>
 
-    <!-- Message shown when primary authentication hasn't been used for some time.  [CHAR LIMIT=52] -->
+    <!-- Message shown when primary authentication hasn't been used for some time.  [CHAR LIMIT=70] -->
     <string name="kg_prompt_password_auth_timeout">Added security required. Password not used for a while.</string>
 
-    <!-- Message shown when primary authentication hasn't been used for some time.  [CHAR LIMIT=52] -->
+    <!-- Message shown when primary authentication hasn't been used for some time.  [CHAR LIMIT=76] -->
     <string name="kg_prompt_pattern_auth_timeout">Added security required. Pattern not used for a while.</string>
 
-    <!-- Message shown when device hasn't been unlocked for a while.  [CHAR LIMIT=52] -->
+    <!-- Message shown when device hasn't been unlocked for a while.  [CHAR LIMIT=82] -->
     <string name="kg_prompt_auth_timeout">Added security required. Device wasn\u2019t unlocked for a while.</string>
 
-    <!-- Message shown when face unlock is not available after too many failed face authentication attempts.  [CHAR LIMIT=52] -->
+    <!-- Message shown when face unlock is not available after too many failed face authentication attempts.  [CHAR LIMIT=70] -->
     <string name="kg_face_locked_out">Can\u2019t unlock with face. Too many attempts.</string>
 
-    <!-- Message shown when fingerprint unlock isn't available after too many failed fingerprint authentication attempts.  [CHAR LIMIT=52] -->
+    <!-- Message shown when fingerprint unlock isn't available after too many failed fingerprint authentication attempts.  [CHAR LIMIT=75] -->
     <string name="kg_fp_locked_out">Can\u2019t unlock with fingerprint. Too many attempts.</string>
 
-    <!-- Message shown when Trust Agent is disabled.  [CHAR LIMIT=52] -->
+    <!-- Message shown when Trust Agent is disabled.  [CHAR LIMIT=70] -->
     <string name="kg_trust_agent_disabled">Trust agent is unavailable</string>
 
-    <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=52]  -->
+    <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=70]  -->
     <string name="kg_primary_auth_locked_out_pin">Too many attempts with incorrect PIN</string>
 
-    <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=52]  -->
+    <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=70]  -->
     <string name="kg_primary_auth_locked_out_pattern">Too many attempts with incorrect pattern</string>
 
-    <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=52]  -->
+    <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=70]  -->
     <string name="kg_primary_auth_locked_out_password">Too many attempts with incorrect password</string>
 
-    <!-- Countdown message shown after too many failed unlock attempts [CHAR LIMIT=26]-->
+    <!-- Countdown message shown after too many failed unlock attempts [CHAR LIMIT=48]-->
     <string name="kg_too_many_failed_attempts_countdown">{count, plural,
         =1 {Try again in # second.}
         other {Try again in # seconds.}
@@ -296,13 +296,13 @@
     <!-- Description of airplane mode -->
     <string name="airplane_mode">Airplane mode</string>
 
-    <!-- An explanation text that the pattern needs to be solved since the device has just been restarted. [CHAR LIMIT=52] -->
+    <!-- An explanation text that the pattern needs to be solved since the device has just been restarted. [CHAR LIMIT=70] -->
     <string name="kg_prompt_reason_restart_pattern">Pattern is required after device restarts</string>
 
-    <!-- An explanation text that the pin needs to be entered since the device has just been restarted. [CHAR LIMIT=52] -->
+    <!-- An explanation text that the pin needs to be entered since the device has just been restarted. [CHAR LIMIT=70] -->
     <string name="kg_prompt_reason_restart_pin">PIN is required after device restarts</string>
 
-    <!-- An explanation text that the password needs to be entered since the device has just been restarted. [CHAR LIMIT=52] -->
+    <!-- An explanation text that the password needs to be entered since the device has just been restarted. [CHAR LIMIT=70] -->
     <string name="kg_prompt_reason_restart_password">Password is required after device restarts</string>
 
     <!-- An explanation text that the pattern needs to be solved since the user hasn't used strong authentication since quite some time. [CHAR LIMIT=80] -->
diff --git a/packages/SystemUI/res/drawable/chipbar_background.xml b/packages/SystemUI/res/drawable/chipbar_background.xml
index 5722177..7530f5b 100644
--- a/packages/SystemUI/res/drawable/chipbar_background.xml
+++ b/packages/SystemUI/res/drawable/chipbar_background.xml
@@ -17,6 +17,6 @@
 <shape
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
-    <solid android:color="?androidprv:attr/colorAccentSecondary" />
+    <solid android:color="?androidprv:attr/materialColorSecondaryFixed" />
     <corners android:radius="32dp" />
 </shape>
diff --git a/packages/SystemUI/res/drawable/chipbar_end_button_background.xml b/packages/SystemUI/res/drawable/chipbar_end_button_background.xml
index 80c7207..a3832ee 100644
--- a/packages/SystemUI/res/drawable/chipbar_end_button_background.xml
+++ b/packages/SystemUI/res/drawable/chipbar_end_button_background.xml
@@ -20,7 +20,7 @@
     android:color="?android:textColorPrimary">
     <item android:id="@android:id/background">
         <shape>
-            <solid android:color="@android:color/system_accent1_200"/>
+            <solid android:color="?androidprv:attr/materialColorPrimaryFixedDim"/>
             <corners android:radius="24dp" />
         </shape>
     </item>
diff --git a/packages/SystemUI/res/drawable/hearing.xml b/packages/SystemUI/res/drawable/hearing.xml
new file mode 100644
index 0000000..02f5f92
--- /dev/null
+++ b/packages/SystemUI/res/drawable/hearing.xml
@@ -0,0 +1,24 @@
+<!-- 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="48dp"
+    android:height="48dp"
+    android:viewportWidth="48"
+    android:viewportHeight="48"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M34.4,43.95Q31.55,43.95 29.45,42.4Q27.35,40.85 26.35,38.3Q25.35,35.75 24.375,34.325Q23.4,32.9 20.7,30.75Q17.4,28.1 15.95,25.1Q14.5,22.1 14.5,17.8Q14.5,11.8 18.3,7.975Q22.1,4.15 28.1,4.15Q34,4.15 37.875,7.825Q41.75,11.5 42,17.2H39Q38.75,12.8 35.725,9.975Q32.7,7.15 28.1,7.15Q23.6,7.15 20.55,10.225Q17.5,13.3 17.5,17.8Q17.5,21.4 18.9,24.025Q20.3,26.65 23.55,29.1Q25.5,30.55 26.675,32.25Q27.85,33.95 28.9,36.45Q29.75,38.55 31.125,39.75Q32.5,40.95 34.4,40.95Q36.15,40.95 37.425,39.75Q38.7,38.55 38.95,36.8H41.95Q41.7,39.8 39.55,41.875Q37.4,43.95 34.4,43.95ZM11.95,32.9Q9.1,29.75 7.55,25.825Q6,21.9 6,17.6Q6,13.35 7.475,9.375Q8.95,5.4 11.95,2.35L14.2,4.35Q11.6,7 10.3,10.425Q9,13.85 9,17.6Q9,21.3 10.325,24.725Q11.65,28.15 14.2,30.85ZM28.1,22.45Q26.15,22.45 24.8,21.1Q23.45,19.75 23.45,17.8Q23.45,15.85 24.8,14.45Q26.15,13.05 28.1,13.05Q30.05,13.05 31.45,14.45Q32.85,15.85 32.85,17.8Q32.85,19.75 31.45,21.1Q30.05,22.45 28.1,22.45Z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/chipbar.xml b/packages/SystemUI/res/layout/chipbar.xml
index 762dcdc..8fa975b 100644
--- a/packages/SystemUI/res/layout/chipbar.xml
+++ b/packages/SystemUI/res/layout/chipbar.xml
@@ -49,15 +49,17 @@
             android:alpha="0.0"
             />
 
+        <!-- LINT.IfChange textColor -->
         <TextView
             android:id="@+id/text"
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_weight="1"
-            android:textSize="@dimen/chipbar_text_size"
-            android:textColor="@color/chipbar_text_and_icon_color"
+            style="@style/Chipbar.Text"
+            android:textColor="?androidprv:attr/materialColorOnSecondaryFixed"
             android:alpha="0.0"
             />
+        <!-- LINT.ThenChange(systemui.temporarydisplay.chipbar.ChipbarInfo.kt) -->
 
         <!-- At most one of [loading, failure_icon, undo] will be visible at a time. -->
         <ImageView
@@ -66,7 +68,7 @@
             android:layout_height="@dimen/chipbar_end_icon_size"
             android:layout_marginStart="@dimen/chipbar_end_item_start_margin"
             android:src="@drawable/ic_progress_activity"
-            android:tint="@android:color/system_accent2_700"
+            android:tint="?androidprv:attr/materialColorOnSecondaryFixedVariant"
             android:alpha="0.0"
             />
 
@@ -84,9 +86,9 @@
             android:id="@+id/end_button"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:textColor="?androidprv:attr/textColorOnAccent"
             android:layout_marginStart="@dimen/chipbar_end_item_start_margin"
-            android:textSize="@dimen/chipbar_text_size"
+            style="@style/Chipbar.Text"
+            android:textColor="?androidprv:attr/materialColorOnPrimaryFixed"
             android:paddingStart="@dimen/chipbar_outer_padding"
             android:paddingEnd="@dimen/chipbar_outer_padding"
             android:paddingTop="@dimen/chipbar_end_button_vertical_padding"
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index f1fca76..d710676 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -152,10 +152,4 @@
     <include
         layout="@layout/keyguard_bottom_area"
         android:visibility="gone" />
-
-    <FrameLayout
-        android:id="@+id/preview_container"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-    </FrameLayout>
 </com.android.systemui.shade.NotificationPanelView>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 96e6d4e..cb8c2a7 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -231,9 +231,6 @@
 
     <color name="people_tile_background">@color/material_dynamic_secondary95</color>
 
-    <!-- Chipbar -->
-    <color name="chipbar_text_and_icon_color">@android:color/system_accent2_900</color>
-
     <!-- Internet Dialog -->
     <!-- Material next state on color-->
     <color name="settingslib_state_on_color">@color/settingslib_state_on</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 4db42aa..0eb0a07 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1110,6 +1110,7 @@
     <!-- Chipbar -->
     <!-- (Used for media tap-to-transfer chip for sender device and active unlock) -->
     <dimen name="chipbar_outer_padding">16dp</dimen>
+    <dimen name="chipbar_outer_padding_half">8dp</dimen>
     <dimen name="chipbar_text_size">16sp</dimen>
     <dimen name="chipbar_start_icon_size">24dp</dimen>
     <dimen name="chipbar_end_icon_size">20dp</dimen>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 2098aea..a359d3b 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -70,6 +70,15 @@
         <item name="android:fontWeight">700</item>
     </style>
 
+    <style name="Chipbar" />
+
+    <style name="Chipbar.Text" parent="@*android:style/TextAppearance.DeviceDefault.Notification.Title">
+        <!-- Text size should be kept in sync with the notification conversation header size. (The
+             conversation header doesn't have a defined style, so the size must be copied here.)
+             See notification_template_conversation_header.xml. -->
+        <item name="android:textSize">16sp</item>
+    </style>
+
     <style name="TextAppearance" />
 
     <style name="TextAppearance.QS">
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 0779653..7262a73 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -25,6 +25,7 @@
 import android.text.format.DateFormat
 import android.util.TypedValue
 import android.view.View
+import android.view.View.OnAttachStateChangeListener
 import android.view.ViewTreeObserver
 import android.widget.FrameLayout
 import androidx.annotation.VisibleForTesting
@@ -105,6 +106,24 @@
                 }
                 updateFontSizes()
                 updateTimeListeners()
+                value.smallClock.view.addOnAttachStateChangeListener(
+                    object : OnAttachStateChangeListener {
+                        override fun onViewAttachedToWindow(p0: View?) {
+                            value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+                        }
+
+                        override fun onViewDetachedFromWindow(p0: View?) {
+                        }
+                })
+                value.largeClock.view.addOnAttachStateChangeListener(
+                    object : OnAttachStateChangeListener {
+                        override fun onViewAttachedToWindow(p0: View?) {
+                            value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+                        }
+
+                        override fun onViewDetachedFromWindow(p0: View?) {
+                        }
+                })
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 0326b6d..5ba0ad6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -294,11 +294,11 @@
 
     public void dump(PrintWriter pw, String[] args) {
         pw.println("KeyguardClockSwitch:");
-        pw.println("  mSmallClockFrame: " + mSmallClockFrame);
-        pw.println("  mSmallClockFrame.alpha: " + mSmallClockFrame.getAlpha());
-        pw.println("  mLargeClockFrame: " + mLargeClockFrame);
-        pw.println("  mLargeClockFrame.alpha: " + mLargeClockFrame.getAlpha());
-        pw.println("  mStatusArea: " + mStatusArea);
-        pw.println("  mDisplayedClockSize: " + mDisplayedClockSize);
+        pw.println("  mSmallClockFrame = " + mSmallClockFrame);
+        pw.println("  mSmallClockFrame.alpha = " + mSmallClockFrame.getAlpha());
+        pw.println("  mLargeClockFrame = " + mLargeClockFrame);
+        pw.println("  mLargeClockFrame.alpha = " + mLargeClockFrame.getAlpha());
+        pw.println("  mStatusArea = " + mStatusArea);
+        pw.println("  mDisplayedClockSize = " + mDisplayedClockSize);
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 9290220..ad333b7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -109,7 +109,7 @@
     private final ContentObserver mShowWeatherObserver = new ContentObserver(null) {
         @Override
         public void onChange(boolean change) {
-            setDateWeatherVisibility();
+            setWeatherVisibility();
         }
     };
 
@@ -236,6 +236,7 @@
 
         updateDoubleLineClock();
         setDateWeatherVisibility();
+        setWeatherVisibility();
 
         mKeyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
                 mKeyguardUnlockAnimationListener);
@@ -266,6 +267,8 @@
                     mStatusArea.removeView(mDateWeatherView);
                     addDateWeatherView(index);
                 }
+                setDateWeatherVisibility();
+                setWeatherVisibility();
             }
             int index = mStatusArea.indexOfChild(mSmartspaceView);
             if (index >= 0) {
@@ -487,16 +490,19 @@
     }
 
     private void setDateWeatherVisibility() {
-        if (mDateWeatherView != null || mWeatherView != null) {
+        if (mDateWeatherView != null) {
             mUiExecutor.execute(() -> {
-                if (mDateWeatherView != null) {
-                    mDateWeatherView.setVisibility(
-                            clockHasCustomWeatherDataDisplay() ? View.GONE : View.VISIBLE);
-                }
-                if (mWeatherView != null) {
-                    mWeatherView.setVisibility(
-                            mSmartspaceController.isWeatherEnabled() ? View.VISIBLE : View.GONE);
-                }
+                mDateWeatherView.setVisibility(
+                        clockHasCustomWeatherDataDisplay() ? View.INVISIBLE : View.VISIBLE);
+            });
+        }
+    }
+
+    private void setWeatherVisibility() {
+        if (mWeatherView != null) {
+            mUiExecutor.execute(() -> {
+                mWeatherView.setVisibility(
+                        mSmartspaceController.isWeatherEnabled() ? View.VISIBLE : View.GONE);
             });
         }
     }
@@ -513,9 +519,10 @@
 
     @Override
     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
-        pw.println("currentClockSizeLarge=" + (mCurrentClockSize == LARGE));
-        pw.println("mCanShowDoubleLineClock=" + mCanShowDoubleLineClock);
+        pw.println("currentClockSizeLarge: " + (mCurrentClockSize == LARGE));
+        pw.println("mCanShowDoubleLineClock: " + mCanShowDoubleLineClock);
         mView.dump(pw, args);
+        mClockRegistry.dump(pw, args);
         ClockController clock = getClock();
         if (clock != null) {
             clock.dump(pw);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 87a7758..db38d34 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -75,6 +75,7 @@
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
@@ -115,6 +116,7 @@
     private final SessionTracker mSessionTracker;
     private final Optional<SideFpsController> mSideFpsController;
     private final FalsingA11yDelegate mFalsingA11yDelegate;
+    private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
     private int mTranslationY;
     // Whether the volume keys should be handled by keyguard. If true, then
     // they will be handled here for specific media types such as music, otherwise
@@ -300,6 +302,7 @@
         @Override
         public void onSwipeUp() {
             if (!mUpdateMonitor.isFaceDetectionRunning()) {
+                mKeyguardFaceAuthInteractor.onSwipeUpOnBouncer();
                 boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth(
                         FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
                 mKeyguardSecurityCallback.userActivity();
@@ -389,7 +392,8 @@
             FalsingA11yDelegate falsingA11yDelegate,
             TelephonyManager telephonyManager,
             ViewMediatorCallback viewMediatorCallback,
-            AudioManager audioManager
+            AudioManager audioManager,
+            KeyguardFaceAuthInteractor keyguardFaceAuthInteractor
     ) {
         super(view);
         mLockPatternUtils = lockPatternUtils;
@@ -414,6 +418,7 @@
         mTelephonyManager = telephonyManager;
         mViewMediatorCallback = viewMediatorCallback;
         mAudioManager = audioManager;
+        mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index b8e196f..d8e1eb0f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -19,10 +19,12 @@
 import static java.util.Collections.emptySet;
 
 import android.content.Context;
+import android.os.Build;
 import android.os.Trace;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewPropertyAnimator;
 import android.widget.GridLayout;
 
 import com.android.systemui.R;
@@ -118,6 +120,16 @@
     }
 
     @Override
+    public ViewPropertyAnimator animate() {
+        if (Build.IS_DEBUGGABLE) {
+            throw new IllegalArgumentException(
+                    "KeyguardStatusView does not support ViewPropertyAnimator. "
+                            + "Use PropertyAnimator instead.");
+        }
+        return super.animate();
+    }
+
+    @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         Trace.beginSection("KeyguardStatusView#onMeasure");
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 6f54988..0cdef4d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -73,7 +73,7 @@
      */
     private static final int KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION = 1000;
 
-    private static final AnimationProperties CLOCK_ANIMATION_PROPERTIES =
+    public static final AnimationProperties CLOCK_ANIMATION_PROPERTIES =
             new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
 
     private final KeyguardSliceViewController mKeyguardSliceViewController;
@@ -221,16 +221,32 @@
         mView.setImportantForAccessibility(mode);
     }
 
+    @VisibleForTesting
+    void setProperty(AnimatableProperty property, float value, boolean animate) {
+        PropertyAnimator.setProperty(mView, property, value, CLOCK_ANIMATION_PROPERTIES, animate);
+    }
+
     /**
      * Update position of the view with an optional animation
      */
     public void updatePosition(int x, int y, float scale, boolean animate) {
         float oldY = mView.getY();
-        PropertyAnimator.setProperty(mView, AnimatableProperty.Y, y, CLOCK_ANIMATION_PROPERTIES,
-                animate);
+        setProperty(AnimatableProperty.Y, y, animate);
 
-        mKeyguardClockSwitchController.updatePosition(x, scale, CLOCK_ANIMATION_PROPERTIES,
-                animate);
+        ClockController clock = mKeyguardClockSwitchController.getClock();
+        if (clock != null && clock.getConfig().getUseAlternateSmartspaceAODTransition()) {
+            // If requested, scale the entire view instead of just the clock view
+            mKeyguardClockSwitchController.updatePosition(x, 1f /* scale */,
+                    CLOCK_ANIMATION_PROPERTIES, animate);
+            setProperty(AnimatableProperty.SCALE_X, scale, animate);
+            setProperty(AnimatableProperty.SCALE_Y, scale, animate);
+        } else {
+            mKeyguardClockSwitchController.updatePosition(x, scale,
+                    CLOCK_ANIMATION_PROPERTIES, animate);
+            setProperty(AnimatableProperty.SCALE_X, 1f, animate);
+            setProperty(AnimatableProperty.SCALE_Y, 1f, animate);
+        }
+
         if (oldY != y) {
             mKeyguardClockSwitchController.updateKeyguardStatusViewOffset();
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 350c4ed..cc64389 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -154,6 +154,15 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.dump.DumpsysTableLogger;
+import com.android.systemui.keyguard.domain.interactor.FaceAuthenticationListener;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
+import com.android.systemui.keyguard.shared.model.AcquiredAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.AuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.DetectionStatus;
+import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.FailedAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus;
 import com.android.systemui.keyguard.shared.model.SysUiFaceAuthenticateOptions;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.WeatherData;
@@ -372,6 +381,8 @@
     private final FingerprintManager mFpm;
     @Nullable
     private final FaceManager mFaceManager;
+    @Nullable
+    private KeyguardFaceAuthInteractor mFaceAuthInteractor;
     private final LockPatternUtils mLockPatternUtils;
     @VisibleForTesting
     @DevicePostureInt
@@ -1165,8 +1176,21 @@
         Trace.endSection();
     }
 
+    /**
+     * @deprecated This is being migrated to use modern architecture, this method is visible purely
+     * for bridging the gap while the migration is active.
+     */
     private void handleFaceAuthFailed() {
         Assert.isMainThread();
+        String reason =
+                mKeyguardBypassController.canBypass() ? "bypass"
+                        : mAlternateBouncerShowing ? "alternateBouncer"
+                                : mPrimaryBouncerFullyShown ? "bouncer"
+                                        : "udfpsFpDown";
+        requestActiveUnlock(
+                ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
+                "faceFailure-" + reason);
+
         mLogger.d("onFaceAuthFailed");
         mFaceCancelSignal = null;
         setFaceRunningState(BIOMETRIC_STATE_STOPPED);
@@ -1180,6 +1204,10 @@
                 mContext.getString(R.string.kg_face_not_recognized));
     }
 
+    /**
+     * @deprecated This is being migrated to use modern architecture, this method is visible purely
+     * for bridging the gap while the migration is active.
+     */
     private void handleFaceAcquired(int acquireInfo) {
         Assert.isMainThread();
         mLogger.logFaceAcquired(acquireInfo);
@@ -1189,8 +1217,19 @@
                 cb.onBiometricAcquired(FACE, acquireInfo);
             }
         }
+
+        if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
+                acquireInfo)) {
+            requestActiveUnlock(
+                    ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
+                    "faceAcquireInfo-" + acquireInfo);
+        }
     }
 
+    /**
+     * @deprecated This is being migrated to use modern architecture, this method is visible purely
+     * for bridging the gap while the migration is active.
+     */
     private void handleFaceAuthenticated(int authUserId, boolean isStrongBiometric) {
         Trace.beginSection("KeyGuardUpdateMonitor#handlerFaceAuthenticated");
         try {
@@ -1203,7 +1242,7 @@
                 mLogger.logFaceAuthForWrongUser(authUserId);
                 return;
             }
-            if (isFaceDisabled(userId)) {
+            if (!isFaceAuthInteractorEnabled() && isFaceDisabled(userId)) {
                 mLogger.logFaceAuthDisabledForUser(userId);
                 return;
             }
@@ -1215,7 +1254,14 @@
         Trace.endSection();
     }
 
+    /**
+     * @deprecated This is being migrated to use modern architecture, this method is visible purely
+     * for bridging the gap while the migration is active.
+     */
     private void handleFaceHelp(int msgId, String helpString) {
+        if (mFaceAcquiredInfoIgnoreList.contains(msgId)) {
+            return;
+        }
         Assert.isMainThread();
         mLogger.logFaceAuthHelpMsg(msgId, helpString);
         for (int i = 0; i < mCallbacks.size(); i++) {
@@ -1226,22 +1272,10 @@
         }
     }
 
-    private final Runnable mRetryFaceAuthentication = new Runnable() {
-        @Override
-        public void run() {
-            mLogger.logRetryingAfterFaceHwUnavailable(mHardwareFaceUnavailableRetryCount);
-            updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
-                    FACE_AUTH_TRIGGERED_RETRY_AFTER_HW_UNAVAILABLE);
-        }
-    };
-
-    private void onFaceCancelNotReceived() {
-        mLogger.e("Face cancellation not received, transitioning to STOPPED");
-        mFaceRunningState = BIOMETRIC_STATE_STOPPED;
-        KeyguardUpdateMonitor.this.updateFaceListeningState(BIOMETRIC_ACTION_STOP,
-                FACE_AUTH_STOPPED_FACE_CANCEL_NOT_RECEIVED);
-    }
-
+    /**
+     * @deprecated This is being migrated to use modern architecture, this method is visible purely
+     * for bridging the gap while the migration is active.
+     */
     private void handleFaceError(int msgId, final String originalErrMsg) {
         Assert.isMainThread();
         String errString = originalErrMsg;
@@ -1299,6 +1333,28 @@
         if (lockedOutStateChanged) {
             notifyLockedOutStateChanged(FACE);
         }
+
+        if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceError(msgId)) {
+            requestActiveUnlock(
+                    ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
+                    "faceError-" + msgId);
+        }
+    }
+
+    private final Runnable mRetryFaceAuthentication = new Runnable() {
+        @Override
+        public void run() {
+            mLogger.logRetryingAfterFaceHwUnavailable(mHardwareFaceUnavailableRetryCount);
+            updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
+                    FACE_AUTH_TRIGGERED_RETRY_AFTER_HW_UNAVAILABLE);
+        }
+    };
+
+    private void onFaceCancelNotReceived() {
+        mLogger.e("Face cancellation not received, transitioning to STOPPED");
+        mFaceRunningState = BIOMETRIC_STATE_STOPPED;
+        KeyguardUpdateMonitor.this.updateFaceListeningState(BIOMETRIC_ACTION_STOP,
+                FACE_AUTH_STOPPED_FACE_CANCEL_NOT_RECEIVED);
     }
 
     private void handleFaceLockoutReset(@LockoutMode int mode) {
@@ -1343,10 +1399,61 @@
         return mFingerprintRunningState == BIOMETRIC_STATE_RUNNING;
     }
 
+    /**
+     * @deprecated This is being migrated to use modern architecture.
+     */
+    @Deprecated
     public boolean isFaceDetectionRunning() {
+        if (isFaceAuthInteractorEnabled()) {
+            return getFaceAuthInteractor().isRunning();
+        }
         return mFaceRunningState == BIOMETRIC_STATE_RUNNING;
     }
 
+    private boolean isFaceAuthInteractorEnabled() {
+        return mFaceAuthInteractor != null && mFaceAuthInteractor.isEnabled();
+    }
+
+    private @Nullable KeyguardFaceAuthInteractor getFaceAuthInteractor() {
+        return mFaceAuthInteractor;
+    }
+
+    /**
+     * Set the face auth interactor that should be used for initiating face authentication.
+     */
+    public void setFaceAuthInteractor(@Nullable KeyguardFaceAuthInteractor faceAuthInteractor) {
+        mFaceAuthInteractor = faceAuthInteractor;
+        mFaceAuthInteractor.registerListener(mFaceAuthenticationListener);
+    }
+
+    private FaceAuthenticationListener mFaceAuthenticationListener =
+            new FaceAuthenticationListener() {
+                @Override
+                public void onAuthenticationStatusChanged(@NonNull AuthenticationStatus status) {
+                    if (status instanceof AcquiredAuthenticationStatus) {
+                        handleFaceAcquired(
+                                ((AcquiredAuthenticationStatus) status).getAcquiredInfo());
+                    } else if (status instanceof ErrorAuthenticationStatus) {
+                        ErrorAuthenticationStatus error = (ErrorAuthenticationStatus) status;
+                        handleFaceError(error.getMsgId(), error.getMsg());
+                    } else if (status instanceof FailedAuthenticationStatus) {
+                        handleFaceAuthFailed();
+                    } else if (status instanceof HelpAuthenticationStatus) {
+                        HelpAuthenticationStatus helpMsg = (HelpAuthenticationStatus) status;
+                        handleFaceHelp(helpMsg.getMsgId(), helpMsg.getMsg());
+                    } else if (status instanceof SuccessAuthenticationStatus) {
+                        FaceManager.AuthenticationResult result =
+                                ((SuccessAuthenticationStatus) status).getSuccessResult();
+                        handleFaceAuthenticated(result.getUserId(), result.isStrongBiometric());
+                    }
+                }
+
+                @Override
+                public void onDetectionStatusChanged(@NonNull DetectionStatus status) {
+                    handleFaceAuthenticated(status.getUserId(), status.isStrongBiometric());
+                }
+            };
+
     private boolean isTrustDisabled() {
         // Don't allow trust agent if device is secured with a SIM PIN. This is here
         // mainly because there's no other way to prompt the user to enter their SIM PIN
@@ -1360,6 +1467,10 @@
                 || isSimPinSecure();
     }
 
+    /**
+     * @deprecated This method is not needed anymore with the new face auth system.
+     */
+    @Deprecated
     private boolean isFaceDisabled(int userId) {
         // TODO(b/140035044)
         return whitelistIpcs(() ->
@@ -1371,7 +1482,10 @@
     /**
      * @return whether the current user has been authenticated with face. This may be true
      * on the lockscreen if the user doesn't have bypass enabled.
+     *
+     * @deprecated This is being migrated to use modern architecture.
      */
+    @Deprecated
     public boolean getIsFaceAuthenticated() {
         boolean faceAuthenticated = false;
         BiometricAuthenticated bioFaceAuthenticated = mUserFaceAuthenticated.get(getCurrentUser());
@@ -1619,6 +1733,9 @@
     void setAssistantVisible(boolean assistantVisible) {
         mAssistantVisible = assistantVisible;
         mLogger.logAssistantVisible(mAssistantVisible);
+        if (isFaceAuthInteractorEnabled()) {
+            mFaceAuthInteractor.onAssistantTriggeredOnLockScreen();
+        }
         updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
                 FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED);
         if (mAssistantVisible) {
@@ -1832,54 +1949,27 @@
 
                 @Override
                 public void onAuthenticationFailed() {
-                        String reason =
-                                mKeyguardBypassController.canBypass() ? "bypass"
-                                        : mAlternateBouncerShowing ? "alternateBouncer"
-                                                : mPrimaryBouncerFullyShown ? "bouncer"
-                                                        : "udfpsFpDown";
-                        requestActiveUnlock(
-                                ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
-                                "faceFailure-" + reason);
-
                     handleFaceAuthFailed();
                 }
 
                 @Override
                 public void onAuthenticationSucceeded(FaceManager.AuthenticationResult result) {
-                    Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationSucceeded");
                     handleFaceAuthenticated(result.getUserId(), result.isStrongBiometric());
-                    Trace.endSection();
                 }
 
                 @Override
                 public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
-                    if (mFaceAcquiredInfoIgnoreList.contains(helpMsgId)) {
-                        return;
-                    }
                     handleFaceHelp(helpMsgId, helpString.toString());
                 }
 
                 @Override
                 public void onAuthenticationError(int errMsgId, CharSequence errString) {
                     handleFaceError(errMsgId, errString.toString());
-
-                    if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceError(errMsgId)) {
-                        requestActiveUnlock(
-                                ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
-                                "faceError-" + errMsgId);
-                    }
                 }
 
                 @Override
                 public void onAuthenticationAcquired(int acquireInfo) {
                     handleFaceAcquired(acquireInfo);
-
-                    if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
-                            acquireInfo)) {
-                        requestActiveUnlock(
-                                ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
-                                "faceAcquireInfo-" + acquireInfo);
-                    }
                 }
     };
 
@@ -2628,7 +2718,9 @@
      * @param reason One of the reasons {@link FaceAuthApiRequestReason} on why this API is being
      * invoked.
      * @return current face auth detection state, true if it is running.
+     * @deprecated This is being migrated to use modern architecture.
      */
+    @Deprecated
     public boolean requestFaceAuth(@FaceAuthApiRequestReason String reason) {
         mLogger.logFaceAuthRequested(reason);
         updateFaceListeningState(BIOMETRIC_ACTION_START, apiRequestReasonToUiEvent(reason));
@@ -2643,6 +2735,7 @@
     }
 
     private void updateFaceListeningState(int action, @NonNull FaceAuthUiEvent faceAuthUiEvent) {
+        if (isFaceAuthInteractorEnabled()) return;
         // If this message exists, we should not authenticate again until this message is
         // consumed by the handler
         if (mHandler.hasMessages(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE)) {
@@ -3154,6 +3247,9 @@
     }
 
     public boolean isFaceLockedOut() {
+        if (isFaceAuthInteractorEnabled()) {
+            return getFaceAuthInteractor().isLockedOut();
+        }
         return mFaceLockedOutPermanent;
     }
 
@@ -3202,13 +3298,23 @@
         return mIsUnlockWithFingerprintPossible.getOrDefault(userId, false);
     }
 
+    /**
+     * @deprecated This is being migrated to use modern architecture.
+     */
+    @Deprecated
     private boolean isUnlockWithFacePossible(int userId) {
+        if (isFaceAuthInteractorEnabled()) {
+            return getFaceAuthInteractor().canFaceAuthRun();
+        }
         return isFaceAuthEnabledForUser(userId) && !isFaceDisabled(userId);
     }
 
     /**
      * If face hardware is available, user has enrolled and enabled auth via setting.
+     *
+     * @deprecated This is being migrated to use modern architecture.
      */
+    @Deprecated
     public boolean isFaceAuthEnabledForUser(int userId) {
         // TODO (b/242022358), make this rely on onEnrollmentChanged event and update it only once.
         updateFaceEnrolled(userId);
@@ -3232,6 +3338,7 @@
     }
 
     private void stopListeningForFace(@NonNull FaceAuthUiEvent faceAuthUiEvent) {
+        if (isFaceAuthInteractorEnabled()) return;
         mLogger.v("stopListeningForFace()");
         mLogger.logStoppedListeningForFace(mFaceRunningState, faceAuthUiEvent.getReason());
         mUiEventLogger.log(faceAuthUiEvent, getKeyguardSessionId());
@@ -4098,6 +4205,9 @@
         mStatusBarStateController.removeCallback(mStatusBarStateControllerListener);
         mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mPhoneStateListener);
         mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionListener);
+        if (isFaceAuthInteractorEnabled()) {
+            mFaceAuthInteractor.unregisterListener(mFaceAuthenticationListener);
+        }
 
         if (mDeviceProvisionedObserver != null) {
             mContext.getContentResolver().unregisterContentObserver(mDeviceProvisionedObserver);
@@ -4127,6 +4237,7 @@
         pw.println("  getUserHasTrust()=" + getUserHasTrust(getCurrentUser()));
         pw.println("  getUserUnlockedWithBiometric()="
                 + getUserUnlockedWithBiometric(getCurrentUser()));
+        pw.println("  isFaceAuthInteractorEnabled: " + isFaceAuthInteractorEnabled());
         pw.println("  SIM States:");
         for (SimData data : mSimDatas.values()) {
             pw.println("    " + data.toString());
@@ -4262,7 +4373,7 @@
      * Cancels all operations in the scheduler if it is hung for 10 seconds.
      */
     public void startBiometricWatchdog() {
-        if (mFaceManager != null) {
+        if (mFaceManager != null && !isFaceAuthInteractorEnabled()) {
             mFaceManager.scheduleWatchdog();
         }
         if (mFpm != null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index a678edc..651c979 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -18,8 +18,8 @@
 
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 
+import android.util.Property;
 import android.view.View;
-import android.view.ViewPropertyAnimator;
 
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.plugins.log.LogBuffer;
@@ -34,6 +34,8 @@
 
 import com.google.errorprone.annotations.CompileTimeConstant;
 
+import java.util.function.Consumer;
+
 /**
  * Helper class for updating visibility of keyguard views based on keyguard and status bar state.
  * This logic is shared by both the keyguard status view and the keyguard user switcher.
@@ -83,47 +85,49 @@
             boolean keyguardFadingAway,
             boolean goingToFullShade,
             int oldStatusBarState) {
-        mView.animate().cancel();
+        PropertyAnimator.cancelAnimation(mView, AnimatableProperty.ALPHA);
         boolean isOccluded = mKeyguardStateController.isOccluded();
         mKeyguardViewVisibilityAnimating = false;
 
         if ((!keyguardFadingAway && oldStatusBarState == KEYGUARD
                 && statusBarState != KEYGUARD) || goingToFullShade) {
             mKeyguardViewVisibilityAnimating = true;
-            mView.animate()
-                    .alpha(0f)
-                    .setStartDelay(0)
-                    .setDuration(160)
-                    .setInterpolator(Interpolators.ALPHA_OUT)
-                    .withEndAction(
-                            mAnimateKeyguardStatusViewGoneEndRunnable);
+
+            AnimationProperties animProps = new AnimationProperties()
+                    .setCustomInterpolator(View.ALPHA, Interpolators.ALPHA_OUT)
+                    .setAnimationEndAction(mSetGoneEndAction);
             if (keyguardFadingAway) {
-                mView.animate()
-                        .setStartDelay(mKeyguardStateController.getKeyguardFadingAwayDelay())
-                        .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration())
-                        .start();
+                animProps
+                        .setDelay(mKeyguardStateController.getKeyguardFadingAwayDelay())
+                        .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration());
                 log("goingToFullShade && keyguardFadingAway");
             } else {
+                animProps.setDelay(0).setDuration(160);
                 log("goingToFullShade && !keyguardFadingAway");
             }
+            PropertyAnimator.setProperty(
+                    mView, AnimatableProperty.ALPHA, 0f, animProps, true /* animate */);
         } else if (oldStatusBarState == StatusBarState.SHADE_LOCKED && statusBarState == KEYGUARD) {
             mView.setVisibility(View.VISIBLE);
             mKeyguardViewVisibilityAnimating = true;
             mView.setAlpha(0f);
-            mView.animate()
-                    .alpha(1f)
-                    .setStartDelay(0)
-                    .setDuration(320)
-                    .setInterpolator(Interpolators.ALPHA_IN)
-                    .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable);
+            PropertyAnimator.setProperty(
+                    mView, AnimatableProperty.ALPHA, 1f,
+                    new AnimationProperties()
+                            .setDelay(0)
+                            .setDuration(320)
+                            .setCustomInterpolator(View.ALPHA, Interpolators.ALPHA_IN)
+                            .setAnimationEndAction(
+                                    property -> mSetVisibleEndRunnable.run()),
+                    true /* animate */);
             log("keyguardFadingAway transition w/ Y Aniamtion");
         } else if (statusBarState == KEYGUARD) {
             if (keyguardFadingAway) {
                 mKeyguardViewVisibilityAnimating = true;
-                ViewPropertyAnimator animator = mView.animate()
-                        .alpha(0)
-                        .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
-                        .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable);
+                AnimationProperties animProps = new AnimationProperties()
+                        .setDelay(0)
+                        .setCustomInterpolator(View.ALPHA, Interpolators.FAST_OUT_LINEAR_IN)
+                        .setAnimationEndAction(mSetInvisibleEndAction);
                 if (mAnimateYPos) {
                     float target = mView.getY() - mView.getHeight() * 0.05f;
                     int delay = 0;
@@ -135,13 +139,16 @@
                     PropertyAnimator.setProperty(mView, AnimatableProperty.Y, target,
                             mAnimationProperties,
                             true /* animate */);
-                    animator.setDuration(duration)
-                            .setStartDelay(delay);
+                    animProps.setDuration(duration)
+                            .setDelay(delay);
                     log("keyguardFadingAway transition w/ Y Aniamtion");
                 } else {
                     log("keyguardFadingAway transition w/o Y Animation");
                 }
-                animator.start();
+                PropertyAnimator.setProperty(
+                        mView, AnimatableProperty.ALPHA, 0f,
+                        animProps,
+                        true /* animate */);
             } else if (mScreenOffAnimationController.shouldAnimateInKeyguard()) {
                 log("ScreenOff transition");
                 mKeyguardViewVisibilityAnimating = true;
@@ -149,7 +156,7 @@
                 // Ask the screen off animation controller to animate the keyguard visibility for us
                 // since it may need to be cancelled due to keyguard lifecycle events.
                 mScreenOffAnimationController.animateInKeyguard(
-                        mView, mAnimateKeyguardStatusViewVisibleEndRunnable);
+                        mView, mSetVisibleEndRunnable);
             } else {
                 log("Direct set Visibility to VISIBLE");
                 mView.setVisibility(View.VISIBLE);
@@ -163,19 +170,25 @@
         mLastOccludedState = isOccluded;
     }
 
-    private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = () -> {
-        mKeyguardViewVisibilityAnimating = false;
-        mView.setVisibility(View.INVISIBLE);
-        log("Callback Set Visibility to INVISIBLE");
+    private final Consumer<Property> mSetInvisibleEndAction = new Consumer<>() {
+        @Override
+        public void accept(Property property) {
+            mKeyguardViewVisibilityAnimating = false;
+            mView.setVisibility(View.INVISIBLE);
+            log("Callback Set Visibility to INVISIBLE");
+        }
     };
 
-    private final Runnable mAnimateKeyguardStatusViewGoneEndRunnable = () -> {
-        mKeyguardViewVisibilityAnimating = false;
-        mView.setVisibility(View.GONE);
-        log("CallbackSet Visibility to GONE");
+    private final Consumer<Property> mSetGoneEndAction = new Consumer<>() {
+        @Override
+        public void accept(Property property) {
+            mKeyguardViewVisibilityAnimating = false;
+            mView.setVisibility(View.GONE);
+            log("CallbackSet Visibility to GONE");
+        }
     };
 
-    private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = () -> {
+    private final Runnable mSetVisibleEndRunnable = () -> {
         mKeyguardViewVisibilityAnimating = false;
         mView.setVisibility(View.VISIBLE);
         log("Callback Set Visibility to VISIBLE");
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 235a8bc..5f2afe8 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -294,6 +294,11 @@
 
         final CharSequence prevContentDescription = mView.getContentDescription();
         if (mShowLockIcon) {
+            if (wasShowingFpIcon) {
+                // fp icon was shown by UdfpsView, and now we still want to animate the transition
+                // in this drawable
+                mView.updateIcon(ICON_FINGERPRINT, false);
+            }
             mView.updateIcon(ICON_LOCK, false);
             mView.setContentDescription(mLockedLabel);
             mView.setVisibility(View.VISIBLE);
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index 6e98a18..cde8ff7 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -33,6 +33,7 @@
 
 import dagger.Module;
 import dagger.Provides;
+
 import kotlinx.coroutines.CoroutineDispatcher;
 import kotlinx.coroutines.CoroutineScope;
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index e42f051..517f94f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -637,6 +637,7 @@
 
     @Override
     public void onDetachedFromWindow() {
+        mPanelInteractionDetector.disable();
         OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher();
         if (dispatcher != null) {
             findOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mBackCallback);
@@ -674,7 +675,6 @@
 
     @Override
     public void dismissWithoutCallback(boolean animate) {
-        mPanelInteractionDetector.disable();
         if (animate) {
             animateAway(false /* sendReason */, 0 /* reason */);
         } else {
@@ -685,7 +685,6 @@
 
     @Override
     public void dismissFromSystemServer() {
-        mPanelInteractionDetector.disable();
         animateAway(false /* sendReason */, 0 /* reason */);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
index d15a2af..b72801d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
@@ -30,6 +30,7 @@
     @MainThread
     fun disable() {
         if (action != null) {
+            Log.i(TAG, "Disable dectector")
             action = null
             shadeExpansionStateManager.removeExpansionListener(this::onPanelExpansionChanged)
         }
@@ -39,8 +40,8 @@
     private fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) =
         mainExecutor.execute {
             action?.let {
-                if (event.tracking || event.expanded) {
-                    Log.v(TAG, "Detected panel interaction, event: $event")
+                if (event.tracking || (event.expanded && event.fraction > 0)) {
+                    Log.i(TAG, "Detected panel interaction, event: $event")
                     it.onPanelInteraction.run()
                     disable()
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index aabdafb..7a23759 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -83,6 +83,7 @@
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.FalsingManager;
@@ -151,6 +152,7 @@
     @NonNull private final DumpManager mDumpManager;
     @NonNull private final SystemUIDialogManager mDialogManager;
     @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @NonNull private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
     @NonNull private final VibratorHelper mVibrator;
     @NonNull private final FeatureFlags mFeatureFlags;
     @NonNull private final FalsingManager mFalsingManager;
@@ -818,7 +820,8 @@
             @NonNull AlternateBouncerInteractor alternateBouncerInteractor,
             @NonNull SecureSettings secureSettings,
             @NonNull InputManager inputManager,
-            @NonNull UdfpsUtils udfpsUtils) {
+            @NonNull UdfpsUtils udfpsUtils,
+            @NonNull KeyguardFaceAuthInteractor keyguardFaceAuthInteractor) {
         mContext = context;
         mExecution = execution;
         mVibrator = vibrator;
@@ -882,6 +885,7 @@
                     }
                     return Unit.INSTANCE;
                 });
+        mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
 
         final UdfpsOverlayController mUdfpsOverlayController = new UdfpsOverlayController();
         mFingerprintManager.setUdfpsOverlayController(mUdfpsOverlayController);
@@ -1141,6 +1145,7 @@
         if (!mOnFingerDown) {
             playStartHaptic();
 
+            mKeyguardFaceAuthInteractor.onUdfpsSensorTouched();
             if (!mKeyguardUpdateMonitor.isFaceDetectionRunning()) {
                 mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt
index 6a6c3eb..0869351 100644
--- a/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt
@@ -16,10 +16,10 @@
 
 package com.android.systemui.common.shared.model
 
-import androidx.annotation.ColorRes
+import androidx.annotation.AttrRes
 
 /** Models an icon with a specific tint. */
 data class TintedIcon(
     val icon: Icon,
-    @ColorRes val tint: Int?,
+    @AttrRes val tint: Int?,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt
index bcc5932..5c5723f 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.common.ui.binder
 
 import android.widget.ImageView
+import com.android.settingslib.Utils
 import com.android.systemui.common.shared.model.TintedIcon
 
 object TintedIconViewBinder {
@@ -33,7 +34,7 @@
         IconViewBinder.bind(tintedIcon.icon, view)
         view.imageTintList =
             if (tintedIcon.tint != null) {
-                view.resources.getColorStateList(tintedIcon.tint, view.context.theme)
+                Utils.getColorAttr(view.context, tintedIcon.tint)
             } else {
                 null
             }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
index 7f44463..aca621b 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
@@ -41,6 +41,7 @@
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
@@ -195,7 +196,14 @@
          * Called by the monitor when this session is removed.
          */
         private void onRemoved() {
-            mCallbacks.forEach(callback -> callback.onRemoved());
+            mEventListeners.clear();
+            mGestureListeners.clear();
+            final Iterator<Callback> iter = mCallbacks.iterator();
+            while (iter.hasNext()) {
+                final Callback callback = iter.next();
+                callback.onRemoved();
+                iter.remove();
+            }
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 69d7582..f01749a 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -108,11 +108,6 @@
     val NOTIFICATION_SHELF_REFACTOR =
         unreleasedFlag(271161129, "notification_shelf_refactor")
 
-    // TODO(b/263414400): Tracking Bug
-    @JvmField
-    val NOTIFICATION_ANIMATE_BIG_PICTURE =
-        releasedFlag(120, "notification_animate_big_picture")
-
     @JvmField
     val ANIMATED_NOTIFICATION_SHADE_INSETS =
         releasedFlag(270682168, "animated_notification_shade_insets")
@@ -234,7 +229,7 @@
     /** Whether to inflate the bouncer view on a background thread. */
     // TODO(b/273341787): Tracking Bug
     @JvmField
-    val PREVENT_BYPASS_KEYGUARD = unreleasedFlag(230, "prevent_bypass_keyguard", teamfood = true)
+    val PREVENT_BYPASS_KEYGUARD = releasedFlag(230, "prevent_bypass_keyguard")
 
     /** Whether to use a new data source for intents to run on keyguard dismissal. */
     @JvmField
@@ -419,9 +414,6 @@
     // TODO(b/254512758): Tracking Bug
     @JvmField val ROUNDED_BOX_RIPPLE = releasedFlag(1002, "rounded_box_ripple")
 
-    // TODO(b/270882464): Tracking Bug
-    val ENABLE_DOCK_SETUP_V2 = releasedFlag(1005, "enable_dock_setup_v2")
-
     // TODO(b/265045965): Tracking Bug
     val SHOW_LOWLIGHT_ON_DIRECT_BOOT = releasedFlag(1003, "show_lowlight_on_direct_boot")
 
@@ -608,9 +600,6 @@
     // TODO(b/254512507): Tracking Bug
     val CHOOSER_UNBUNDLED = releasedFlag(1500, "chooser_unbundled")
 
-    // TODO(b/266983432) Tracking Bug
-    val SHARESHEET_CUSTOM_ACTIONS = releasedFlag(1501, "sharesheet_custom_actions")
-
     // TODO(b/266982749) Tracking Bug
     val SHARESHEET_RESELECTION_ACTION = releasedFlag(1502, "sharesheet_reselection_action")
 
@@ -704,6 +693,11 @@
     val KEYBOARD_BACKLIGHT_INDICATOR =
             unreleasedFlag(2601, "keyboard_backlight_indicator", teamfood = true)
 
+    // TODO(b/277192623): Tracking Bug
+    @JvmField
+    val KEYBOARD_EDUCATION =
+        unreleasedFlag(2603, "keyboard_education", teamfood = false)
+
     // TODO(b/272036292): Tracking Bug
     @JvmField
     val LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt
new file mode 100644
index 0000000..a44df7e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt
@@ -0,0 +1,35 @@
+/*
+ *   Copyright (C) 2023 The Android Open Source Project
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ */
+
+package com.android.systemui.keyguard.dagger
+
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
+import com.android.systemui.keyguard.domain.interactor.NoopKeyguardFaceAuthInteractor
+import dagger.Binds
+import dagger.Module
+
+/**
+ * Module that provides bindings for face auth classes that are injected into SysUI components that
+ * are used across different SysUI variants, where face auth is not supported.
+ *
+ * Some variants that do not support face authentication can install this module to provide a no-op
+ * implementation of the interactor.
+ */
+@Module
+interface KeyguardFaceAuthNotSupportedModule {
+    @Binds
+    fun keyguardFaceAuthInteractor(impl: NoopKeyguardFaceAuthInteractor): KeyguardFaceAuthInteractor
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index 56e7398..5f6098b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.AcquiredAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.AuthenticationStatus
 import com.android.systemui.keyguard.shared.model.DetectionStatus
@@ -44,6 +45,8 @@
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.log.FaceAuthenticationLogger
 import com.android.systemui.log.SessionTracker
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.user.data.repository.UserRepository
 import java.io.PrintWriter
@@ -78,7 +81,7 @@
     val isAuthenticated: Flow<Boolean>
 
     /** Whether face auth can run at this point. */
-    val canRunFaceAuth: Flow<Boolean>
+    val canRunFaceAuth: StateFlow<Boolean>
 
     /** Provide the current status of face authentication. */
     val authenticationStatus: Flow<AuthenticationStatus>
@@ -87,10 +90,13 @@
     val detectionStatus: Flow<DetectionStatus>
 
     /** Current state of whether face authentication is locked out or not. */
-    val isLockedOut: Flow<Boolean>
+    val isLockedOut: StateFlow<Boolean>
 
     /** Current state of whether face authentication is running. */
-    val isAuthRunning: Flow<Boolean>
+    val isAuthRunning: StateFlow<Boolean>
+
+    /** Whether bypass is currently enabled */
+    val isBypassEnabled: Flow<Boolean>
 
     /**
      * Trigger face authentication.
@@ -126,6 +132,9 @@
     private val keyguardRepository: KeyguardRepository,
     private val keyguardInteractor: KeyguardInteractor,
     private val alternateBouncerInteractor: AlternateBouncerInteractor,
+    @FaceDetectTableLog private val faceDetectLog: TableLogBuffer,
+    @FaceAuthTableLog private val faceAuthLog: TableLogBuffer,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     dumpManager: DumpManager,
 ) : DeviceEntryFaceAuthRepository, Dumpable {
     private var authCancellationSignal: CancellationSignal? = null
@@ -166,7 +175,7 @@
     override val isAuthenticated: Flow<Boolean>
         get() = _isAuthenticated
 
-    private val bypassEnabled: Flow<Boolean> =
+    override val isBypassEnabled: Flow<Boolean> =
         keyguardBypassController?.let {
             conflatedCallbackFlow {
                 val callback =
@@ -204,6 +213,13 @@
         observeFaceAuthGatingChecks()
         observeFaceDetectGatingChecks()
         observeFaceAuthResettingConditions()
+        listenForSchedulingWatchdog()
+    }
+
+    private fun listenForSchedulingWatchdog() {
+        keyguardTransitionInteractor.anyStateToGoneTransition
+            .onEach { faceManager?.scheduleWatchdog() }
+            .launchIn(applicationScope)
     }
 
     private fun observeFaceAuthResettingConditions() {
@@ -221,17 +237,19 @@
         // Face detection can run only when lockscreen bypass is enabled
         // & detection is supported & biometric unlock is not allowed.
         listOf(
-                canFaceAuthOrDetectRun(),
-                logAndObserve(bypassEnabled, "bypassEnabled"),
+                canFaceAuthOrDetectRun(faceDetectLog),
+                logAndObserve(isBypassEnabled, "isBypassEnabled", faceDetectLog),
                 logAndObserve(
                     biometricSettingsRepository.isNonStrongBiometricAllowed.isFalse(),
-                    "nonStrongBiometricIsNotAllowed"
+                    "nonStrongBiometricIsNotAllowed",
+                    faceDetectLog
                 ),
                 // We don't want to run face detect if it's not possible to authenticate with FP
                 // from the bouncer. UDFPS is the only fp sensor type that won't support this.
                 logAndObserve(
                     and(isUdfps(), deviceEntryFingerprintAuthRepository.isRunning).isFalse(),
-                    "udfpsAuthIsNotPossibleAnymore"
+                    "udfpsAuthIsNotPossibleAnymore",
+                    faceDetectLog
                 )
             )
             .reduce(::and)
@@ -243,6 +261,7 @@
                     cancelDetection()
                 }
             }
+            .logDiffsForTable(faceDetectLog, "", "canFaceDetectRun", false)
             .launchIn(applicationScope)
     }
 
@@ -251,26 +270,34 @@
             it == BiometricType.UNDER_DISPLAY_FINGERPRINT
         }
 
-    private fun canFaceAuthOrDetectRun(): Flow<Boolean> {
+    private fun canFaceAuthOrDetectRun(tableLogBuffer: TableLogBuffer): Flow<Boolean> {
         return listOf(
-                logAndObserve(biometricSettingsRepository.isFaceEnrolled, "isFaceEnrolled"),
+                logAndObserve(
+                    biometricSettingsRepository.isFaceEnrolled,
+                    "isFaceEnrolled",
+                    tableLogBuffer
+                ),
                 logAndObserve(
                     biometricSettingsRepository.isFaceAuthenticationEnabled,
-                    "isFaceAuthenticationEnabled"
+                    "isFaceAuthenticationEnabled",
+                    tableLogBuffer
                 ),
                 logAndObserve(
                     userRepository.userSwitchingInProgress.isFalse(),
-                    "userSwitchingNotInProgress"
+                    "userSwitchingNotInProgress",
+                    tableLogBuffer
                 ),
                 logAndObserve(
                     keyguardRepository.isKeyguardGoingAway.isFalse(),
-                    "keyguardNotGoingAway"
+                    "keyguardNotGoingAway",
+                    tableLogBuffer
                 ),
                 logAndObserve(
                     keyguardRepository.wakefulness
                         .map { WakefulnessModel.isSleepingOrStartingToSleep(it) }
                         .isFalse(),
-                    "deviceNotSleepingOrNotStartingToSleep"
+                    "deviceNotSleepingOrNotStartingToSleep",
+                    tableLogBuffer
                 ),
                 logAndObserve(
                     combine(
@@ -279,15 +306,18 @@
                     ) { a, b ->
                         !a || b
                     },
-                    "secureCameraNotActiveOrAltBouncerIsShowing"
+                    "secureCameraNotActiveOrAltBouncerIsShowing",
+                    tableLogBuffer
                 ),
                 logAndObserve(
                     biometricSettingsRepository.isFaceAuthSupportedInCurrentPosture,
-                    "isFaceAuthSupportedInCurrentPosture"
+                    "isFaceAuthSupportedInCurrentPosture",
+                    tableLogBuffer
                 ),
                 logAndObserve(
                     biometricSettingsRepository.isCurrentUserInLockdown.isFalse(),
-                    "userHasNotLockedDownDevice"
+                    "userHasNotLockedDownDevice",
+                    tableLogBuffer
                 )
             )
             .reduce(::and)
@@ -296,20 +326,27 @@
     private fun observeFaceAuthGatingChecks() {
         // Face auth can run only if all of the gating conditions are true.
         listOf(
-                canFaceAuthOrDetectRun(),
-                logAndObserve(isLockedOut.isFalse(), "isNotLocked"),
+                canFaceAuthOrDetectRun(faceAuthLog),
+                logAndObserve(isLockedOut.isFalse(), "isNotInLockOutState", faceAuthLog),
                 logAndObserve(
                     deviceEntryFingerprintAuthRepository.isLockedOut.isFalse(),
-                    "fpLockedOut"
+                    "fpLockedOut",
+                    faceAuthLog
                 ),
-                logAndObserve(trustRepository.isCurrentUserTrusted.isFalse(), "currentUserTrusted"),
+                logAndObserve(
+                    trustRepository.isCurrentUserTrusted.isFalse(),
+                    "currentUserTrusted",
+                    faceAuthLog
+                ),
                 logAndObserve(
                     biometricSettingsRepository.isNonStrongBiometricAllowed,
-                    "nonStrongBiometricIsAllowed"
+                    "nonStrongBiometricIsAllowed",
+                    faceAuthLog
                 ),
                 logAndObserve(
                     userRepository.selectedUserInfo.map { it.isPrimary },
-                    "userIsPrimaryUser"
+                    "userIsPrimaryUser",
+                    faceAuthLog
                 ),
             )
             .reduce(::and)
@@ -323,6 +360,7 @@
                     cancel()
                 }
             }
+            .logDiffsForTable(faceAuthLog, "", "canFaceAuthRun", false)
             .launchIn(applicationScope)
     }
 
@@ -337,7 +375,6 @@
 
             override fun onAuthenticationAcquired(acquireInfo: Int) {
                 _authenticationStatus.value = AcquiredAuthenticationStatus(acquireInfo)
-                faceAuthLogger.authenticationAcquired(acquireInfo)
             }
 
             override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
@@ -398,7 +435,7 @@
 
     override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
         if (_isAuthRunning.value) {
-            faceAuthLogger.ignoredFaceAuthTrigger(uiEvent)
+            faceAuthLogger.ignoredFaceAuthTrigger(uiEvent, "face auth is currently running")
             return
         }
 
@@ -435,7 +472,16 @@
                 )
             }
         } else if (fallbackToDetection && canRunDetection.value) {
+            faceAuthLogger.ignoredFaceAuthTrigger(
+                uiEvent,
+                "face auth gating check is false, falling back to detection."
+            )
             detect()
+        } else {
+            faceAuthLogger.ignoredFaceAuthTrigger(
+                uiEvent,
+                "face auth & detect gating check is false"
+            )
         }
     }
 
@@ -464,7 +510,7 @@
     private val currentUserId: Int
         get() = userRepository.getSelectedUserInfo().id
 
-    fun cancelDetection() {
+    private fun cancelDetection() {
         detectCancellationSignal?.cancel()
         detectCancellationSignal = null
     }
@@ -488,10 +534,20 @@
         _isAuthRunning.value = false
     }
 
-    private fun logAndObserve(cond: Flow<Boolean>, loggingContext: String): Flow<Boolean> {
-        return cond.distinctUntilChanged().onEach {
-            faceAuthLogger.observedConditionChanged(it, loggingContext)
-        }
+    private fun logAndObserve(
+        cond: Flow<Boolean>,
+        conditionName: String,
+        logBuffer: TableLogBuffer
+    ): Flow<Boolean> {
+        return cond
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                logBuffer,
+                columnName = conditionName,
+                columnPrefix = "",
+                initialValue = false
+            )
+            .onEach { faceAuthLogger.observedConditionChanged(it, conditionName) }
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceAuthTableLog.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceAuthTableLog.kt
new file mode 100644
index 0000000..6c23032
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceAuthTableLog.kt
@@ -0,0 +1,25 @@
+/*
+ *   Copyright (C) 2023 The Android Open Source Project
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import javax.inject.Qualifier
+
+/** Face auth logs in table format. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class FaceAuthTableLog
diff --git a/core/java/com/android/internal/expresslog/Utils.java b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceDetectTableLog.kt
similarity index 72%
rename from core/java/com/android/internal/expresslog/Utils.java
rename to packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceDetectTableLog.kt
index d82192f..342064f 100644
--- a/core/java/com/android/internal/expresslog/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceDetectTableLog.kt
@@ -14,8 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.internal.expresslog;
+package com.android.systemui.keyguard.data.repository
 
-final class Utils {
-    static native long hashString(String stringToHash);
-}
+import javax.inject.Qualifier
+
+/** Face detect logs in table format. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class FaceDetectTableLog
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
index 3c66f24..ef8b401 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
@@ -17,8 +17,17 @@
 
 package com.android.systemui.keyguard.data.repository
 
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
+import com.android.systemui.keyguard.domain.interactor.SystemUIKeyguardFaceAuthInteractor
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableLogBufferFactory
 import dagger.Binds
 import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
 
 @Module
 interface KeyguardFaceAuthModule {
@@ -27,5 +36,31 @@
         impl: DeviceEntryFaceAuthRepositoryImpl
     ): DeviceEntryFaceAuthRepository
 
+    @Binds
+    @IntoMap
+    @ClassKey(SystemUIKeyguardFaceAuthInteractor::class)
+    fun bind(impl: SystemUIKeyguardFaceAuthInteractor): CoreStartable
+
+    @Binds
+    fun keyguardFaceAuthInteractor(
+        impl: SystemUIKeyguardFaceAuthInteractor
+    ): KeyguardFaceAuthInteractor
+
     @Binds fun trustRepository(impl: TrustRepositoryImpl): TrustRepository
+
+    companion object {
+        @Provides
+        @SysUISingleton
+        @FaceAuthTableLog
+        fun provideFaceAuthTableLog(factory: TableLogBufferFactory): TableLogBuffer {
+            return factory.create("FaceAuthTableLog", 100)
+        }
+
+        @Provides
+        @SysUISingleton
+        @FaceDetectTableLog
+        fun provideFaceDetectTableLog(factory: TableLogBufferFactory): TableLogBuffer {
+            return factory.create("FaceDetectTableLog", 100)
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
new file mode 100644
index 0000000..06ae11fe8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
@@ -0,0 +1,78 @@
+/*
+ *   Copyright (C) 2023 The Android Open Source Project
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.shared.model.AuthenticationStatus
+import com.android.systemui.keyguard.shared.model.DetectionStatus
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Interactor that exposes API to get the face authentication status and handle any events that can
+ * cause face authentication to run.
+ */
+interface KeyguardFaceAuthInteractor {
+
+    /** Current authentication status */
+    val authenticationStatus: Flow<AuthenticationStatus>
+
+    /** Current detection status */
+    val detectionStatus: Flow<DetectionStatus>
+
+    /** Can face auth be run right now */
+    fun canFaceAuthRun(): Boolean
+
+    /** Whether face auth is currently running or not. */
+    fun isRunning(): Boolean
+
+    /** Whether face auth is in lock out state. */
+    fun isLockedOut(): Boolean
+
+    /**
+     * Register listener for use from code that cannot use [authenticationStatus] or
+     * [detectionStatus]
+     */
+    fun registerListener(listener: FaceAuthenticationListener)
+
+    /** Unregister previously registered listener */
+    fun unregisterListener(listener: FaceAuthenticationListener)
+
+    /** Whether the face auth interactor is enabled or not. */
+    fun isEnabled(): Boolean
+
+    fun onUdfpsSensorTouched()
+    fun onAssistantTriggeredOnLockScreen()
+    fun onDeviceLifted()
+    fun onQsExpansionStared()
+    fun onNotificationPanelClicked()
+    fun onSwipeUpOnBouncer()
+}
+
+/**
+ * Listener that can be registered with the [KeyguardFaceAuthInteractor] to receive updates about
+ * face authentication & detection updates.
+ *
+ * This is present to make it easier for use the new face auth API for code that cannot use
+ * [KeyguardFaceAuthInteractor.authenticationStatus] or [KeyguardFaceAuthInteractor.detectionStatus]
+ * flows.
+ */
+interface FaceAuthenticationListener {
+    /** Receive face authentication status updates */
+    fun onAuthenticationStatusChanged(status: AuthenticationStatus)
+
+    /** Receive status updates whenever face detection runs */
+    fun onDetectionStatusChanged(status: DetectionStatus)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index aabd212..da0ada1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -78,6 +78,14 @@
     val primaryBouncerToGoneTransition: Flow<TransitionStep> =
         repository.transition(PRIMARY_BOUNCER, GONE)
 
+    /** OFF->LOCKSCREEN transition information. */
+    val offToLockscreenTransition: Flow<TransitionStep> =
+        repository.transition(KeyguardState.OFF, LOCKSCREEN)
+
+    /** DOZING->LOCKSCREEN transition information. */
+    val dozingToLockscreenTransition: Flow<TransitionStep> =
+        repository.transition(KeyguardState.DOZING, LOCKSCREEN)
+
     /**
      * AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <->
      * Lockscreen (0f).
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
new file mode 100644
index 0000000..cad40aa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
@@ -0,0 +1,62 @@
+/*
+ *   Copyright (C) 2023 The Android Open Source Project
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.AuthenticationStatus
+import com.android.systemui.keyguard.shared.model.DetectionStatus
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+
+/**
+ * Implementation of the interactor that noops all face auth operations.
+ *
+ * This is required for SystemUI variants that do not support face authentication but still inject
+ * other SysUI components that depend on [KeyguardFaceAuthInteractor]
+ */
+@SysUISingleton
+class NoopKeyguardFaceAuthInteractor @Inject constructor() : KeyguardFaceAuthInteractor {
+    override val authenticationStatus: Flow<AuthenticationStatus>
+        get() = emptyFlow()
+    override val detectionStatus: Flow<DetectionStatus>
+        get() = emptyFlow()
+
+    override fun canFaceAuthRun(): Boolean = false
+
+    override fun isRunning(): Boolean = false
+
+    override fun isLockedOut(): Boolean = false
+
+    override fun isEnabled() = false
+
+    override fun registerListener(listener: FaceAuthenticationListener) {}
+
+    override fun unregisterListener(listener: FaceAuthenticationListener) {}
+
+    override fun onUdfpsSensorTouched() {}
+
+    override fun onAssistantTriggeredOnLockScreen() {}
+
+    override fun onDeviceLifted() {}
+
+    override fun onQsExpansionStared() {}
+
+    override fun onNotificationPanelClicked() {}
+
+    override fun onSwipeUpOnBouncer() {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
new file mode 100644
index 0000000..20ebb71
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
@@ -0,0 +1,200 @@
+/*
+ *   Copyright (C) 2023 The Android Open Source Project
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.keyguard.FaceAuthUiEvent
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.log.FaceAuthenticationLogger
+import com.android.systemui.util.kotlin.pairwise
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+
+/**
+ * Encapsulates business logic related face authentication being triggered for device entry from
+ * SystemUI Keyguard.
+ */
+@SysUISingleton
+class SystemUIKeyguardFaceAuthInteractor
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    @Main private val mainDispatcher: CoroutineDispatcher,
+    private val repository: DeviceEntryFaceAuthRepository,
+    private val primaryBouncerInteractor: PrimaryBouncerInteractor,
+    private val alternateBouncerInteractor: AlternateBouncerInteractor,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    private val featureFlags: FeatureFlags,
+    private val faceAuthenticationLogger: FaceAuthenticationLogger,
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+) : CoreStartable, KeyguardFaceAuthInteractor {
+
+    private val listeners: MutableList<FaceAuthenticationListener> = mutableListOf()
+
+    override fun start() {
+        if (!isEnabled()) {
+            return
+        }
+        // This is required because fingerprint state required for the face auth repository is
+        // backed by KeyguardUpdateMonitor. KeyguardUpdateMonitor constructor accesses the biometric
+        // state which makes lazy injection not an option.
+        keyguardUpdateMonitor.setFaceAuthInteractor(this)
+        observeFaceAuthStateUpdates()
+        faceAuthenticationLogger.interactorStarted()
+        primaryBouncerInteractor.isShowing
+            .whenItFlipsToTrue()
+            .onEach {
+                faceAuthenticationLogger.bouncerVisibilityChanged()
+                runFaceAuth(
+                    FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN,
+                    fallbackToDetect = true
+                )
+            }
+            .launchIn(applicationScope)
+
+        alternateBouncerInteractor.isVisible
+            .whenItFlipsToTrue()
+            .onEach {
+                faceAuthenticationLogger.alternateBouncerVisibilityChanged()
+                runFaceAuth(
+                    FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN,
+                    fallbackToDetect = false
+                )
+            }
+            .launchIn(applicationScope)
+
+        merge(
+                keyguardTransitionInteractor.aodToLockscreenTransition,
+                keyguardTransitionInteractor.offToLockscreenTransition,
+                keyguardTransitionInteractor.dozingToLockscreenTransition
+            )
+            .filter { it.transitionState == TransitionState.STARTED }
+            .onEach {
+                faceAuthenticationLogger.lockscreenBecameVisible(it)
+                runFaceAuth(
+                    FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED,
+                    fallbackToDetect = true
+                )
+            }
+            .launchIn(applicationScope)
+    }
+
+    override fun onSwipeUpOnBouncer() {
+        runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, false)
+    }
+
+    override fun onNotificationPanelClicked() {
+        runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED, true)
+    }
+
+    override fun onQsExpansionStared() {
+        runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED, true)
+    }
+
+    override fun onDeviceLifted() {
+        runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED, true)
+    }
+
+    override fun onAssistantTriggeredOnLockScreen() {
+        runFaceAuth(FaceAuthUiEvent.FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED, true)
+    }
+
+    override fun onUdfpsSensorTouched() {
+        runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_UDFPS_POINTER_DOWN, false)
+    }
+
+    override fun registerListener(listener: FaceAuthenticationListener) {
+        listeners.add(listener)
+    }
+
+    override fun unregisterListener(listener: FaceAuthenticationListener) {
+        listeners.remove(listener)
+    }
+
+    override fun isLockedOut(): Boolean = repository.isLockedOut.value
+
+    override fun isRunning(): Boolean = repository.isAuthRunning.value
+
+    override fun canFaceAuthRun(): Boolean = repository.canRunFaceAuth.value
+
+    override fun isEnabled(): Boolean {
+        return featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)
+    }
+
+    /** Provide the status of face authentication */
+    override val authenticationStatus = repository.authenticationStatus
+
+    /** Provide the status of face detection */
+    override val detectionStatus = repository.detectionStatus
+
+    private fun runFaceAuth(uiEvent: FaceAuthUiEvent, fallbackToDetect: Boolean) {
+        if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) {
+            applicationScope.launch {
+                faceAuthenticationLogger.authRequested(uiEvent)
+                repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect)
+            }
+        } else {
+            faceAuthenticationLogger.ignoredFaceAuthTrigger(
+                uiEvent,
+                ignoredReason = "Skipping face auth request because feature flag is false"
+            )
+        }
+    }
+
+    private fun observeFaceAuthStateUpdates() {
+        authenticationStatus
+            .onEach { authStatusUpdate ->
+                listeners.forEach { it.onAuthenticationStatusChanged(authStatusUpdate) }
+            }
+            .flowOn(mainDispatcher)
+            .launchIn(applicationScope)
+        detectionStatus
+            .onEach { detectionStatusUpdate ->
+                listeners.forEach { it.onDetectionStatusChanged(detectionStatusUpdate) }
+            }
+            .flowOn(mainDispatcher)
+            .launchIn(applicationScope)
+    }
+
+    companion object {
+        const val TAG = "KeyguardFaceAuthInteractor"
+    }
+}
+
+// Extension method that filters a generic Boolean flow to one that emits
+// whenever there is flip from false -> true
+private fun Flow<Boolean>.whenItFlipsToTrue(): Flow<Boolean> {
+    return this.pairwise()
+        .filter { pair -> !pair.previousValue && pair.newValue }
+        .map { it.newValue }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
index f7355d5..7f6e4a9 100644
--- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
@@ -4,6 +4,7 @@
 import android.hardware.face.FaceSensorPropertiesInternal
 import com.android.keyguard.FaceAuthUiEvent
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.log.dagger.FaceAuthLog
 import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.plugins.log.LogLevel.DEBUG
@@ -27,15 +28,15 @@
 constructor(
     @FaceAuthLog private val logBuffer: LogBuffer,
 ) {
-    fun ignoredFaceAuthTrigger(uiEvent: FaceAuthUiEvent) {
+    fun ignoredFaceAuthTrigger(uiEvent: FaceAuthUiEvent, ignoredReason: String) {
         logBuffer.log(
             TAG,
             DEBUG,
-            { str1 = uiEvent.reason },
             {
-                "Ignoring trigger because face auth is currently running. " +
-                    "Trigger reason: $str1"
-            }
+                str1 = uiEvent.reason
+                str2 = ignoredReason
+            },
+            { "Ignoring trigger because $str2, Trigger reason: $str1" }
         )
     }
 
@@ -135,15 +136,6 @@
         logBuffer.log(TAG, DEBUG, "Face authentication failed")
     }
 
-    fun authenticationAcquired(acquireInfo: Int) {
-        logBuffer.log(
-            TAG,
-            DEBUG,
-            { int1 = acquireInfo },
-            { "Face acquired during face authentication: acquireInfo: $int1 " }
-        )
-    }
-
     fun authenticationError(
         errorCode: Int,
         errString: CharSequence?,
@@ -217,4 +209,34 @@
     fun cancellingFaceAuth() {
         logBuffer.log(TAG, DEBUG, "cancelling face auth because a gating condition became false")
     }
+
+    fun interactorStarted() {
+        logBuffer.log(TAG, DEBUG, "KeyguardFaceAuthInteractor started")
+    }
+
+    fun bouncerVisibilityChanged() {
+        logBuffer.log(TAG, DEBUG, "Triggering face auth because primary bouncer is visible")
+    }
+
+    fun alternateBouncerVisibilityChanged() {
+        logBuffer.log(TAG, DEBUG, "Triggering face auth because alternate bouncer is visible")
+    }
+
+    fun lockscreenBecameVisible(transitionStep: TransitionStep?) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            { str1 = "$transitionStep" },
+            { "Triggering face auth because lockscreen became visible due to transition: $str1" }
+        )
+    }
+
+    fun authRequested(uiEvent: FaceAuthUiEvent) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            { str1 = "$uiEvent" },
+            { "Requesting face auth for trigger: $str1" }
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 4cc0410..fa42114 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -49,6 +49,7 @@
 import android.support.v4.media.MediaMetadataCompat
 import android.text.TextUtils
 import android.util.Log
+import android.util.Pair as APair
 import androidx.media.utils.MediaConstants
 import com.android.internal.logging.InstanceId
 import com.android.keyguard.KeyguardUpdateMonitor
@@ -217,6 +218,13 @@
     private var smartspaceSession: SmartspaceSession? = null
     private var allowMediaRecommendations = allowMediaRecommendations(context)
 
+    private val artworkWidth =
+        context.resources.getDimensionPixelSize(
+            com.android.internal.R.dimen.config_mediaMetadataBitmapMaxSize
+        )
+    private val artworkHeight =
+        context.resources.getDimensionPixelSize(R.dimen.qs_media_session_height_expanded)
+
     /** Check whether this notification is an RCN */
     private fun isRemoteCastNotification(sbn: StatusBarNotification): Boolean {
         return sbn.notification.extras.containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE)
@@ -1250,9 +1258,21 @@
             return null
         }
 
-        val source = ImageDecoder.createSource(context.getContentResolver(), uri)
+        val source = ImageDecoder.createSource(context.contentResolver, uri)
         return try {
-            ImageDecoder.decodeBitmap(source) { decoder, _, _ ->
+            ImageDecoder.decodeBitmap(source) { decoder, info, _ ->
+                val width = info.size.width
+                val height = info.size.height
+                val scale =
+                    MediaDataUtils.getScaleFactor(
+                        APair(width, height),
+                        APair(artworkWidth, artworkHeight)
+                    )
+
+                // Downscale if needed
+                if (scale != 0f && scale < 1) {
+                    decoder.setTargetSize((scale * width).toInt(), (scale * height).toInt())
+                }
                 decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
             }
         } catch (e: IOException) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index cb1f12cf..40027a1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -56,6 +56,7 @@
 import android.os.Trace;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.Pair;
 import android.util.TypedValue;
 import android.view.Gravity;
 import android.view.View;
@@ -122,6 +123,11 @@
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.time.SystemClock;
 
+import dagger.Lazy;
+
+import kotlin.Triple;
+import kotlin.Unit;
+
 import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.List;
@@ -129,10 +135,6 @@
 
 import javax.inject.Inject;
 
-import dagger.Lazy;
-import kotlin.Triple;
-import kotlin.Unit;
-
 /**
  * A view controller used for Media Playback.
  */
@@ -1000,18 +1002,9 @@
 
         int width = drawable.getIntrinsicWidth();
         int height = drawable.getIntrinsicHeight();
-        if (width == 0 || height == 0 || targetWidth == 0 || targetHeight == 0) {
-            return;
-        }
-
-        float scale;
-        if ((width / (float) height) > (targetWidth / (float) targetHeight)) {
-            // Drawable is wider than target view, scale to match height
-            scale = targetHeight / (float) height;
-        } else {
-            // Drawable is taller than target view, scale to match width
-            scale = targetWidth / (float) width;
-        }
+        float scale = MediaDataUtils.getScaleFactor(new Pair(width, height),
+                new Pair(targetWidth, targetHeight));
+        if (scale == 0) return;
         transitionDrawable.setLayerSize(layer, (int) (scale * width), (int) (scale * height));
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java
index e95106e..0239d36 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java
@@ -22,6 +22,7 @@
 import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.text.TextUtils;
+import android.util.Pair;
 
 import androidx.core.math.MathUtils;
 import androidx.media.utils.MediaConstants;
@@ -87,4 +88,32 @@
         }
         return null;
     }
+
+    /**
+     * Calculate a scale factor that will allow the input to fill the target size.
+     *
+     * @param input width, height of the input view
+     * @param target width, height of the target view
+     * @return the scale factor; 0 if any given dimension is 0
+     */
+    public static float getScaleFactor(Pair<Integer, Integer> input,
+            Pair<Integer, Integer> target) {
+        float width = (float) input.first;
+        float height = (float) input.second;
+
+        float targetWidth = (float) target.first;
+        float targetHeight = (float) target.second;
+
+        if (width == 0 || height == 0 || targetWidth == 0 || targetHeight == 0) {
+            return 0f;
+        }
+
+        if ((width / height) > (targetWidth / targetHeight)) {
+            // Input is wider than target view, scale to match height
+            return targetHeight / height;
+        } else {
+            // Input is taller than target view, scale to match width
+            return targetWidth / width;
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
index dbc2a5e..b29b588 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
@@ -19,7 +19,7 @@
 import android.content.Context
 import android.content.pm.PackageManager
 import android.graphics.drawable.Drawable
-import androidx.annotation.ColorRes
+import androidx.annotation.AttrRes
 import androidx.annotation.DrawableRes
 import com.android.systemui.R
 import com.android.systemui.common.shared.model.ContentDescription
@@ -108,7 +108,7 @@
 data class IconInfo(
     val contentDescription: ContentDescription,
     val icon: MediaTttIcon,
-    @ColorRes val tint: Int?,
+    @AttrRes val tint: Int?,
     /**
      * True if [drawable] is the app's icon, and false if [drawable] is some generic default icon.
      */
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 26b0e8d..b9ef916 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -55,6 +55,7 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.Surface;
+import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
 import android.view.WindowInsets;
 import android.view.WindowManager;
@@ -173,7 +174,7 @@
                 }
             };
 
-
+    private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
     private final Context mContext;
     private final UserTracker mUserTracker;
     private final OverviewProxyService mOverviewProxyService;
@@ -901,6 +902,10 @@
                 Log.d(DEBUG_MISSING_GESTURE_TAG, "Start gesture: " + ev);
             }
 
+            // ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new
+            // ACTION_DOWN, in that case we should just reuse the old instance.
+            mVelocityTracker.clear();
+
             // Verify if this is in within the touch region and we aren't in immersive mode, and
             // either the bouncer is showing or the notification panel is hidden
             mInputEventReceiver.setBatchingEnabled(false);
@@ -1027,11 +1032,30 @@
 
     private void dispatchToBackAnimation(MotionEvent event) {
         if (mBackAnimation != null) {
+            mVelocityTracker.addMovement(event);
+
+            final float velocityX;
+            final float velocityY;
+            if (event.getAction() == MotionEvent.ACTION_UP) {
+                // Compute the current velocity is expensive (see computeCurrentVelocity), so we
+                // are only doing it when the user completes the gesture.
+                int unitPixelPerSecond = 1000;
+                int maxVelocity = mViewConfiguration.getScaledMaximumFlingVelocity();
+                mVelocityTracker.computeCurrentVelocity(unitPixelPerSecond, maxVelocity);
+                velocityX = mVelocityTracker.getXVelocity();
+                velocityY = mVelocityTracker.getYVelocity();
+            } else {
+                velocityX = Float.NaN;
+                velocityY = Float.NaN;
+            }
+
             mBackAnimation.onBackMotion(
-                    event.getX(),
-                    event.getY(),
-                    event.getActionMasked(),
-                    mIsOnLeftEdge ? BackEvent.EDGE_LEFT : BackEvent.EDGE_RIGHT);
+                    /* touchX = */ event.getX(),
+                    /* touchY = */ event.getY(),
+                    /* velocityX = */ velocityX,
+                    /* velocityY = */ velocityY,
+                    /* keyAction = */ event.getActionMasked(),
+                    /* swipeEdge = */ mIsOnLeftEdge ? BackEvent.EDGE_LEFT : BackEvent.EDGE_RIGHT);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index c2c1306..a765702 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -18,6 +18,10 @@
 
 import static android.app.PendingIntent.FLAG_IMMUTABLE;
 
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_CONFIRMATION;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_LOW_WARNING;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SaverManualEnabledReason;
+
 import android.app.Dialog;
 import android.app.KeyguardManager;
 import android.app.Notification;
@@ -691,7 +695,7 @@
             d.setTitle(R.string.battery_saver_confirmation_title);
             d.setPositiveButton(R.string.battery_saver_confirmation_ok,
                     (dialog, which) -> {
-                        setSaverMode(true, false);
+                        setSaverMode(true, false, SAVER_ENABLED_CONFIRMATION);
                         logEvent(BatteryWarningEvents.LowBatteryWarningEvent.SAVER_CONFIRM_OK);
                     });
             d.setNegativeButton(android.R.string.cancel, (dialog, which) ->
@@ -790,8 +794,9 @@
         return builder;
     }
 
-    private void setSaverMode(boolean mode, boolean needFirstTimeWarning) {
-        BatterySaverUtils.setPowerSaveMode(mContext, mode, needFirstTimeWarning);
+    private void setSaverMode(boolean mode, boolean needFirstTimeWarning,
+            @SaverManualEnabledReason int reason) {
+        BatterySaverUtils.setPowerSaveMode(mContext, mode, needFirstTimeWarning, reason);
     }
 
     private void startBatterySaverSchedulePage() {
@@ -839,7 +844,7 @@
             } else if (action.equals(ACTION_START_SAVER)) {
                 logEvent(BatteryWarningEvents
                         .LowBatteryWarningEvent.LOW_BATTERY_NOTIFICATION_TURN_ON);
-                setSaverMode(true, true);
+                setSaverMode(true, true, SAVER_ENABLED_LOW_WARNING);
                 dismissLowBatteryNotification();
             } else if (action.equals(ACTION_SHOW_START_SAVER_CONFIRMATION)) {
                 dismissLowBatteryNotification();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index 57b479e..856c64a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -20,8 +20,6 @@
 import android.os.Build;
 import android.provider.Settings;
 
-import com.android.internal.logging.InstanceId;
-import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
 import com.android.systemui.plugins.qs.QSFactory;
 import com.android.systemui.plugins.qs.QSTile;
@@ -55,11 +53,9 @@
         return tiles;
     }
 
-    void warn(String message, Throwable t);
     Context getContext();
     Context getUserContext();
     int getUserId();
-    UiEventLogger getUiEventLogger();
     Collection<QSTile> getTiles();
     void addCallback(Callback callback);
     void removeCallback(Callback callback);
@@ -107,8 +103,6 @@
 
     int indexOf(String tileSpec);
 
-    InstanceId getNewInstanceId();
-
     interface Callback {
         void onTilesChanged();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
index 14acb4b..67927a4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
@@ -19,8 +19,6 @@
 import android.content.ComponentName
 import android.content.Context
 import androidx.annotation.GuardedBy
-import com.android.internal.logging.InstanceId
-import com.android.internal.logging.UiEventLogger
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dump.DumpManager
@@ -39,10 +37,9 @@
 
 /**
  * Adapter to determine what real class to use for classes that depend on [QSHost].
- *
  * * When [Flags.QS_PIPELINE_NEW_HOST] is off, all calls will be routed to [QSTileHost].
  * * When [Flags.QS_PIPELINE_NEW_HOST] is on, calls regarding the current set of tiles will be
- *   routed to [CurrentTilesInteractor]. Other calls (like [warn]) will still be routed to
+ *   routed to [CurrentTilesInteractor]. Other calls (like [createTileView]) will still be routed to
  *   [QSTileHost].
  *
  * This routing also includes dumps.
@@ -71,10 +68,7 @@
     init {
         scope.launch { tileServiceRequestControllerBuilder.create(this@QSHostAdapter).init() }
         // Redirect dump to the correct host (needed for CTS tests)
-        dumpManager.registerCriticalDumpable(
-            TAG,
-            if (useNewHost) interactor else qsTileHost
-        )
+        dumpManager.registerCriticalDumpable(TAG, if (useNewHost) interactor else qsTileHost)
     }
 
     override fun getTiles(): Collection<QSTile> {
@@ -103,10 +97,7 @@
 
     override fun addCallback(callback: QSHost.Callback) {
         if (useNewHost) {
-            val job =
-                scope.launch {
-                    interactor.currentTiles.collect { callback.onTilesChanged() }
-                }
+            val job = scope.launch { interactor.currentTiles.collect { callback.onTilesChanged() } }
             synchronized(callbacksMap) { callbacksMap.put(callback, job) }
         } else {
             qsTileHost.addCallback(callback)
@@ -147,10 +138,7 @@
 
     override fun addTile(component: ComponentName, end: Boolean) {
         if (useNewHost) {
-            interactor.addTile(
-                TileSpec.create(component),
-                if (end) POSITION_AT_END else 0
-            )
+            interactor.addTile(TileSpec.create(component), if (end) POSITION_AT_END else 0)
         } else {
             qsTileHost.addTile(component, end)
         }
@@ -164,10 +152,6 @@
         }
     }
 
-    override fun warn(message: String?, t: Throwable?) {
-        qsTileHost.warn(message, t)
-    }
-
     override fun getContext(): Context {
         return if (useNewHost) {
             context
@@ -192,10 +176,6 @@
         }
     }
 
-    override fun getUiEventLogger(): UiEventLogger {
-        return qsTileHost.uiEventLogger
-    }
-
     override fun createTileView(
         themedContext: Context?,
         tile: QSTile?,
@@ -219,8 +199,4 @@
     override fun indexOf(tileSpec: String): Int {
         return specs.indexOf(tileSpec)
     }
-
-    override fun getNewInstanceId(): InstanceId {
-        return qsTileHost.newInstanceId
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 0ca8973..59b94b7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -29,9 +29,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.InstanceId;
-import com.android.internal.logging.InstanceIdSequence;
-import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.Dumpable;
 import com.android.systemui.ProtoDumpable;
 import com.android.systemui.R;
@@ -91,7 +88,6 @@
         PanelInteractor, CustomTileAddedRepository {
     private static final String TAG = "QSTileHost";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-    private static final int MAX_QS_INSTANCE_ID = 1 << 20;
 
     // Shared prefs that hold tile lifecycle info.
     @VisibleForTesting
@@ -103,8 +99,6 @@
     private final TunerService mTunerService;
     private final PluginManager mPluginManager;
     private final QSLogger mQSLogger;
-    private final UiEventLogger mUiEventLogger;
-    private final InstanceIdSequence mInstanceIdSequence;
     private final CustomTileStatePersister mCustomTileStatePersister;
     private final Executor mMainExecutor;
     private final UserFileManager mUserFileManager;
@@ -137,7 +131,6 @@
             Provider<AutoTileManager> autoTiles,
             Optional<CentralSurfaces> centralSurfacesOptional,
             QSLogger qsLogger,
-            UiEventLogger uiEventLogger,
             UserTracker userTracker,
             SecureSettings secureSettings,
             CustomTileStatePersister customTileStatePersister,
@@ -150,13 +143,11 @@
         mTunerService = tunerService;
         mPluginManager = pluginManager;
         mQSLogger = qsLogger;
-        mUiEventLogger = uiEventLogger;
         mMainExecutor = mainExecutor;
         mTileLifeCycleManagerFactory = tileLifecycleManagerFactory;
         mUserFileManager = userFileManager;
         mFeatureFlags = featureFlags;
 
-        mInstanceIdSequence = new InstanceIdSequence(MAX_QS_INSTANCE_ID);
         mCentralSurfacesOptional = centralSurfacesOptional;
 
         mQsFactories.add(defaultFactory);
@@ -175,11 +166,6 @@
         });
     }
 
-    @Override
-    public InstanceId getNewInstanceId() {
-        return mInstanceIdSequence.newInstanceId();
-    }
-
     public void destroy() {
         mTiles.values().forEach(tile -> tile.destroy());
         mAutoTiles.destroy();
@@ -207,11 +193,6 @@
     }
 
     @Override
-    public UiEventLogger getUiEventLogger() {
-        return mUiEventLogger;
-    }
-
-    @Override
     public void addCallback(Callback callback) {
         mCallbacks.add(callback);
     }
@@ -227,11 +208,6 @@
     }
 
     @Override
-    public void warn(String message, Throwable t) {
-        // already logged
-    }
-
-    @Override
     public void collapsePanels() {
         mCentralSurfacesOptional.ifPresent(CentralSurfaces::postAnimateCollapsePanels);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QsEventLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/QsEventLogger.kt
new file mode 100644
index 0000000..fc739ed
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QsEventLogger.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.qs
+
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.InstanceIdSequence
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+interface QsEventLogger : UiEventLogger {
+    fun getNewInstanceId(): InstanceId
+}
+
+@SysUISingleton
+class QsEventLoggerImpl
+@Inject
+constructor(
+    uiEventLogger: UiEventLogger,
+) : QsEventLogger, UiEventLogger by uiEventLogger {
+
+    companion object {
+        private const val MAX_QS_INSTANCE_ID = 1 shl 20
+    }
+
+    val sequence = InstanceIdSequence(MAX_QS_INSTANCE_ID)
+    override fun getNewInstanceId(): InstanceId {
+        return sequence.newInstanceId()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
index 3ddd9f1c..1f63f5d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
@@ -21,6 +21,8 @@
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.QSHostAdapter
 import com.android.systemui.qs.QSTileHost
+import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.QsEventLoggerImpl
 import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository
 import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedSharedPrefsRepository
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
@@ -34,8 +36,12 @@
 
     @Binds fun provideQsHost(controllerImpl: QSHostAdapter): QSHost
 
+    @Binds fun provideEventLogger(impl: QsEventLoggerImpl): QsEventLogger
+
     @Module
     companion object {
+        private const val MAX_QS_INSTANCE_ID = 1 shl 20
+
         @Provides
         @JvmStatic
         fun providePanelInteractor(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index d4854e1..897b0e7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -59,17 +59,20 @@
 import com.android.systemui.plugins.qs.QSTile.State;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.settings.DisplayTracker;
 
+import dagger.Lazy;
+
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import javax.inject.Inject;
 
-import dagger.Lazy;
+
 
 public class CustomTile extends QSTileImpl<State> implements TileChangeListener {
     public static final String PREFIX = "custom(";
@@ -111,6 +114,7 @@
 
     private CustomTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             Looper backgroundLooper,
             Handler mainHandler,
             FalsingManager falsingManager,
@@ -124,7 +128,7 @@
             TileServices tileServices,
             DisplayTracker displayTracker
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mTileServices = tileServices;
         mWindowManager = WindowManagerGlobal.getWindowManagerService();
@@ -561,6 +565,7 @@
 
     public static class Builder {
         final Lazy<QSHost> mQSHostLazy;
+        final QsEventLogger mUiEventLogger;
         final Looper mBackgroundLooper;
         final Handler mMainHandler;
         private final FalsingManager mFalsingManager;
@@ -578,6 +583,7 @@
         @Inject
         public Builder(
                 Lazy<QSHost> hostLazy,
+                QsEventLogger uiEventLogger,
                 @Background Looper backgroundLooper,
                 @Main Handler mainHandler,
                 FalsingManager falsingManager,
@@ -590,6 +596,7 @@
                 DisplayTracker displayTracker
         ) {
             mQSHostLazy = hostLazy;
+            mUiEventLogger = uiEventLogger;
             mBackgroundLooper = backgroundLooper;
             mMainHandler = mainHandler;
             mFalsingManager = falsingManager;
@@ -620,6 +627,7 @@
             String action = getAction(mSpec);
             return new CustomTile(
                     mQSHostLazy.get(),
+                    mUiEventLogger,
                     mBackgroundLooper,
                     mMainHandler,
                     mFalsingManager,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 49ba508..2a9e7d0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -66,6 +66,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSEvent;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.SideLabelTileLayout;
 import com.android.systemui.qs.logging.QSLogger;
 
@@ -179,6 +180,7 @@
 
     protected QSTileImpl(
             QSHost host,
+            QsEventLogger uiEventLogger,
             Looper backgroundLooper,
             Handler mainHandler,
             FalsingManager falsingManager,
@@ -189,8 +191,8 @@
     ) {
         mHost = host;
         mContext = host.getContext();
-        mInstanceId = host.getNewInstanceId();
-        mUiEventLogger = host.getUiEventLogger();
+        mInstanceId = uiEventLogger.getNewInstanceId();
+        mUiEventLogger = uiEventLogger;
 
         mUiHandler = mainHandler;
         mHandler = new H(backgroundLooper);
@@ -633,7 +635,6 @@
             } catch (Throwable t) {
                 final String error = "Error in " + name;
                 Log.w(TAG, error, t);
-                mHost.warn(error, t);
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
index 92a83bb..30765f7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -45,15 +45,18 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.settings.GlobalSettings;
 
+import dagger.Lazy;
+
 import javax.inject.Inject;
 
-import dagger.Lazy;
+
 
 /** Quick settings tile: Airplane mode **/
 public class AirplaneModeTile extends QSTileImpl<BooleanState> {
@@ -69,6 +72,7 @@
     @Inject
     public AirplaneModeTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -81,7 +85,7 @@
             GlobalSettings globalSettings,
             UserTracker userTracker
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mBroadcastDispatcher = broadcastDispatcher;
         mLazyConnectivityManager = lazyConnectivityManager;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
index 2ca452e..c709969 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.settings.UserTracker
@@ -31,6 +32,7 @@
 
 class AlarmTile @Inject constructor(
     host: QSHost,
+    uiEventLogger: QsEventLogger,
     @Background backgroundLooper: Looper,
     @Main mainHandler: Handler,
     falsingManager: FalsingManager,
@@ -42,6 +44,7 @@
     nextAlarmController: NextAlarmController
 ) : QSTileImpl<QSTile.State>(
     host,
+    uiEventLogger,
     backgroundLooper,
     mainHandler,
     falsingManager,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
index 027a464..a444e76 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
@@ -37,6 +37,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -62,6 +63,7 @@
     @Inject
     public BatterySaverTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -72,7 +74,7 @@
             BatteryController batteryController,
             SecureSettings secureSettings
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mBatteryController = batteryController;
         mBatteryController.observe(getLifecycle(), this);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 08fe270..30218a6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -47,6 +47,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.BluetoothController;
@@ -74,6 +75,7 @@
     @Inject
     public BluetoothTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -83,7 +85,7 @@
             QSLogger qsLogger,
             BluetoothController bluetoothController
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mController = bluetoothController;
         mController.observe(getLifecycle(), mCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java
index 93e5f1e..65ef6b9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java
@@ -37,6 +37,7 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -48,7 +49,9 @@
     public static final String TILE_SPEC = "cameratoggle";
 
     @Inject
-    protected CameraToggleTile(QSHost host,
+    protected CameraToggleTile(
+            QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             MetricsLogger metricsLogger,
@@ -58,7 +61,7 @@
             QSLogger qsLogger,
             IndividualSensorPrivacyController sensorPrivacyController,
             KeyguardStateController keyguardStateController) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger, sensorPrivacyController,
                 keyguardStateController);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 8d98481..54376fe 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -47,6 +47,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.connectivity.NetworkController;
@@ -84,6 +85,7 @@
     @Inject
     public CastTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -97,7 +99,7 @@
             HotspotController hotspotController,
             DialogLaunchAnimator dialogLaunchAnimator
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mController = castController;
         mKeyguard = keyguardStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java
index b6205d5..cf9e346 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java
@@ -37,6 +37,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -56,6 +57,7 @@
     @Inject
     public ColorCorrectionTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -66,7 +68,7 @@
             UserTracker userTracker,
             SecureSettings secureSettings
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
 
         mSetting = new SettingObserver(secureSettings, mHandler,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index 9a44e83..4ecde61 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -38,6 +38,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -55,6 +56,7 @@
     @Inject
     public ColorInversionTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -65,7 +67,7 @@
             UserTracker userTracker,
             SecureSettings secureSettings
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
 
         mSetting = new SettingObserver(secureSettings, mHandler,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
index add517e..e769b5e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
@@ -38,6 +38,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -58,6 +59,7 @@
     @Inject
     public DataSaverTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -68,7 +70,7 @@
             DataSaverController dataSaverController,
             DialogLaunchAnimator dialogLaunchAnimator
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mDataSaverController = dataSaverController;
         mDialogLaunchAnimator = dialogLaunchAnimator;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
index 01164fb..ddaff3b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
@@ -40,6 +40,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import java.util.concurrent.atomic.AtomicBoolean
@@ -47,6 +48,7 @@
 
 class DeviceControlsTile @Inject constructor(
     host: QSHost,
+    uiEventLogger: QsEventLogger,
     @Background backgroundLooper: Looper,
     @Main mainHandler: Handler,
     falsingManager: FalsingManager,
@@ -56,14 +58,15 @@
     qsLogger: QSLogger,
     private val controlsComponent: ControlsComponent
 ) : QSTileImpl<QSTile.State>(
-        host,
-        backgroundLooper,
-        mainHandler,
-        falsingManager,
-        metricsLogger,
-        statusBarStateController,
-        activityStarter,
-        qsLogger
+    host,
+    uiEventLogger,
+    backgroundLooper,
+    mainHandler,
+    falsingManager,
+    metricsLogger,
+    statusBarStateController,
+    activityStarter,
+    qsLogger
 ) {
 
     private var hasControlsApps = AtomicBoolean(false)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 434fe45..3e7bdd1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -54,6 +54,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -88,6 +89,7 @@
     @Inject
     public DndTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -100,7 +102,7 @@
             SecureSettings secureSettings,
             DialogLaunchAnimator dialogLaunchAnimator
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mController = zenModeController;
         mSharedPreferences = sharedPreferences;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
index f913326..eef4c1d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
@@ -48,6 +48,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -90,6 +91,7 @@
     @Inject
     public DreamTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -105,7 +107,7 @@
             @Named(DreamModule.DREAM_ONLY_ENABLED_FOR_DOCK_USER)
                     boolean dreamOnlyEnabledForDockUser
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mDreamManager = dreamManager;
         mBroadcastDispatcher = broadcastDispatcher;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
index e091a75..2c986da 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -37,6 +37,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.FlashlightController;
@@ -55,6 +56,7 @@
     @Inject
     public FlashlightTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -64,7 +66,7 @@
             QSLogger qsLogger,
             FlashlightController flashlightController
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mFlashlightController = flashlightController;
         mFlashlightController.observe(getLifecycle(), this);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
index 3f514344..12d9847 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.statusbar.phone.SystemUIDialog
@@ -47,6 +48,7 @@
 @Inject
 constructor(
     host: QSHost,
+    uiEventLogger: QsEventLogger,
     @Background backgroundLooper: Looper,
     @Main mainHandler: Handler,
     falsingManager: FalsingManager,
@@ -61,6 +63,7 @@
 ) :
     QSTileImpl<QSTile.State?>(
         host,
+        uiEventLogger,
         backgroundLooper,
         mainHandler,
         falsingManager,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index 6bf8b76..4c3699c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -41,6 +41,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.DataSaverController;
@@ -61,6 +62,7 @@
     @Inject
     public HotspotTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -71,7 +73,7 @@
             HotspotController hotspotController,
             DataSaverController dataSaverController
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mHotspotController = hotspotController;
         mDataSaverController = dataSaverController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 75d0172..f16f0dc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -51,6 +51,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.AlphaControlledSignalTileView;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
@@ -90,6 +91,7 @@
     @Inject
     public InternetTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -101,7 +103,7 @@
             AccessPointController accessPointController,
             InternetDialogFactory internetDialogFactory
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mInternetDialogFactory = internetDialogFactory;
         mHandler = mainHandler;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index 27f5826..83c5688 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -37,6 +37,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -59,6 +60,7 @@
     @Inject
     public LocationTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -70,7 +72,7 @@
             KeyguardStateController keyguardStateController,
             PanelInteractor panelInteractor
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mController = locationController;
         mKeyguard = keyguardStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java
index 2e475d4..86a6a8c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java
@@ -37,6 +37,7 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -48,7 +49,9 @@
     public static final String TILE_SPEC = "mictoggle";
 
     @Inject
-    protected MicrophoneToggleTile(QSHost host,
+    protected MicrophoneToggleTile(
+            QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             MetricsLogger metricsLogger,
@@ -58,7 +61,7 @@
             QSLogger qsLogger,
             IndividualSensorPrivacyController sensorPrivacyController,
             KeyguardStateController keyguardStateController) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger, sensorPrivacyController,
                 keyguardStateController);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
index e189f80..29ccb76 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
@@ -43,6 +43,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 
@@ -65,6 +66,7 @@
     @Inject
     public NfcTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -74,7 +76,7 @@
             QSLogger qsLogger,
             BroadcastDispatcher broadcastDispatcher
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mBroadcastDispatcher = broadcastDispatcher;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
index aacd53b..405e139 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -45,6 +45,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.LocationController;
@@ -80,6 +81,7 @@
     @Inject
     public NightDisplayTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -91,7 +93,7 @@
             ColorDisplayManager colorDisplayManager,
             NightDisplayListenerModule.Builder nightDisplayListenerBuilder
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mLocationController = locationController;
         mManager = colorDisplayManager;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
index ae67d99..1eb317a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
@@ -36,6 +36,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -57,6 +58,7 @@
     @Inject
     public OneHandedModeTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -66,7 +68,7 @@
             QSLogger qsLogger,
             UserTracker userTracker,
             SecureSettings secureSettings) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mSetting = new SettingObserver(secureSettings, mHandler,
                 Settings.Secure.ONE_HANDED_MODE_ENABLED, userTracker.getUserId()) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
index 92f5272..9e365d3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
@@ -37,6 +37,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 
@@ -62,6 +63,7 @@
     @Inject
     public QRCodeScannerTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -70,7 +72,7 @@
             ActivityStarter activityStarter,
             QSLogger qsLogger,
             QRCodeScannerController qrCodeScannerController) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mQRCodeScannerController = qrCodeScannerController;
         mQRCodeScannerController.observe(getLifecycle(), mCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
index 4a3c563..e026bdb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
@@ -49,6 +49,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -83,6 +84,7 @@
     @Inject
     public QuickAccessWalletTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -94,7 +96,7 @@
             PackageManager packageManager,
             SecureSettings secureSettings,
             QuickAccessWalletController quickAccessWalletController) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mController = quickAccessWalletController;
         mKeyguardStateController = keyguardStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java
index 10f1ce4..2e04afb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java
@@ -38,6 +38,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.ReduceBrightColorsController;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -59,6 +60,7 @@
             @Named(RBC_AVAILABLE) boolean isAvailable,
             ReduceBrightColorsController reduceBrightColorsController,
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -67,7 +69,7 @@
             ActivityStarter activityStarter,
             QSLogger qsLogger
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mReduceBrightColorsController = reduceBrightColorsController;
         mReduceBrightColorsController.observe(getLifecycle(), this);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index 8888c73..7f7f8ad6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -44,6 +44,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -71,6 +72,7 @@
     @Inject
     public RotationLockTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -83,7 +85,7 @@
             BatteryController batteryController,
             SecureSettings secureSettings
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mController = rotationLockController;
         mController.observe(this, mCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index 65592a7..2d4652d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -41,6 +41,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -74,6 +75,7 @@
     @Inject
     public ScreenRecordTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -88,7 +90,7 @@
             DialogLaunchAnimator dialogLaunchAnimator,
             PanelInteractor panelInteractor
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mController = controller;
         mController.observe(this, mCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
index d99c1d1..7c4f097 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
@@ -39,6 +39,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
@@ -68,7 +69,9 @@
      */
     public abstract String getRestriction();
 
-    protected SensorPrivacyToggleTile(QSHost host,
+    protected SensorPrivacyToggleTile(
+            QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -78,7 +81,7 @@
             QSLogger qsLogger,
             IndividualSensorPrivacyController sensorPrivacyController,
             KeyguardStateController keyguardStateController) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mSensorPrivacyController = sensorPrivacyController;
         mKeyguard = keyguardStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
index 809689c..a60d1ad 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
@@ -39,6 +39,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -69,6 +70,7 @@
     @Inject
     public UiModeNightTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -80,7 +82,7 @@
             BatteryController batteryController,
             LocationController locationController
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mBatteryController = batteryController;
         mUiModeManager = host.getUserContext().getSystemService(UiModeManager.class);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index 6a5c990..17e72e5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -40,6 +40,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
@@ -59,6 +60,7 @@
     @Inject
     public WorkModeTile(
             QSHost host,
+            QsEventLogger uiEventLogger,
             @Background Looper backgroundLooper,
             @Main Handler mainHandler,
             FalsingManager falsingManager,
@@ -68,7 +70,7 @@
             QSLogger qsLogger,
             ManagedProfileController managedProfileController
     ) {
-        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mProfileController = managedProfileController;
         mProfileController.observe(getLifecycle(), this);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index cffe45f..0a188e0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.recents;
 
+import static android.content.Intent.ACTION_PACKAGE_ADDED;
 import static android.content.Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST;
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
 import static android.view.MotionEvent.ACTION_CANCEL;
@@ -48,6 +49,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
+import android.content.pm.ResolveInfo;
 import android.graphics.Region;
 import android.hardware.input.InputManager;
 import android.hardware.input.InputManagerGlobal;
@@ -114,6 +116,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
@@ -393,20 +396,29 @@
     private final BroadcastReceiver mLauncherStateChangedReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            StringBuilder extraComponentList = new StringBuilder(" components: ");
-            if (intent.hasExtra(EXTRA_CHANGED_COMPONENT_NAME_LIST)) {
-                String[] comps = intent.getStringArrayExtra(EXTRA_CHANGED_COMPONENT_NAME_LIST);
-                if (comps != null) {
-                    for (String c : comps) {
-                        extraComponentList.append(c).append(", ");
-                    }
+            // If adding, bind immediately
+            if (Objects.equals(intent.getAction(), ACTION_PACKAGE_ADDED)) {
+                updateEnabledAndBinding();
+                return;
+            }
+
+            // ACTION_PACKAGE_CHANGED
+            String[] compsList = intent.getStringArrayExtra(EXTRA_CHANGED_COMPONENT_NAME_LIST);
+            if (compsList == null) {
+                return;
+            }
+
+            // Only rebind for TouchInteractionService component from launcher
+            ResolveInfo ri = context.getPackageManager()
+                    .resolveService(new Intent(ACTION_QUICKSTEP), 0);
+            String interestingComponent = ri.serviceInfo.name;
+            for (String component : compsList) {
+                if (interestingComponent.equals(component)) {
+                    Log.i(TAG_OPS, "Rebinding for component [" + component + "] change");
+                    updateEnabledAndBinding();
+                    return;
                 }
             }
-            Log.d(TAG_OPS, "launcherStateChanged intent: " + intent + extraComponentList);
-            updateEnabledState();
-
-            // Reconnect immediately, instead of waiting for resume to arrive.
-            startConnectionToCurrentUser();
         }
     };
 
@@ -621,8 +633,7 @@
         screenLifecycle.addObserver(mScreenLifecycleObserver);
         wakefulnessLifecycle.addObserver(mWakefulnessLifecycleObserver);
         // Connect to the service
-        updateEnabledState();
-        startConnectionToCurrentUser();
+        updateEnabledAndBinding();
 
         // Listen for assistant changes
         assistUtils.registerVoiceInteractionSessionListener(mVoiceInteractionSessionListener);
@@ -644,6 +655,10 @@
     private void dispatchNavigationBarSurface() {
         try {
             if (mOverviewProxy != null) {
+                // Catch all for cases where the surface is no longer valid
+                if (mNavigationBarSurface != null && !mNavigationBarSurface.isValid()) {
+                    mNavigationBarSurface = null;
+                }
                 mOverviewProxy.onNavigationBarSurface(mNavigationBarSurface);
             }
         } catch (RemoteException e) {
@@ -651,6 +666,11 @@
         }
     }
 
+    private void updateEnabledAndBinding() {
+        updateEnabledState();
+        startConnectionToCurrentUser();
+    }
+
     private void updateSystemUiStateFlags() {
         final NavigationBar navBarFragment =
                 mNavBarControllerLazy.get().getDefaultNavigationBar();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index aedd976..a4a7d4c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -129,6 +129,7 @@
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants;
@@ -330,6 +331,7 @@
     private final PulseExpansionHandler mPulseExpansionHandler;
     private final KeyguardBypassController mKeyguardBypassController;
     private final KeyguardUpdateMonitor mUpdateMonitor;
+    private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
     private final ConversationNotificationManager mConversationNotificationManager;
     private final AuthController mAuthController;
     private final MediaHierarchyManager mMediaHierarchyManager;
@@ -747,7 +749,8 @@
             DumpManager dumpManager,
             KeyguardLongPressViewModel keyguardLongPressViewModel,
             KeyguardInteractor keyguardInteractor,
-            ActivityStarter activityStarter) {
+            ActivityStarter activityStarter,
+            KeyguardFaceAuthInteractor keyguardFaceAuthInteractor) {
         mInteractionJankMonitor = interactionJankMonitor;
         keyguardStateController.addCallback(new KeyguardStateController.Callback() {
             @Override
@@ -891,6 +894,7 @@
         mScreenOffAnimationController = screenOffAnimationController;
         mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
         mLastDownEvents = new NPVCDownEventState.Buffer(MAX_DOWN_EVENT_BUFFER_SIZE);
+        mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
 
         int currentMode = navigationModeController.addListener(
                 mode -> mIsGestureNavigation = QuickStepContract.isGesturalMode(mode));
@@ -2764,6 +2768,7 @@
                     mShadeLog.v("onMiddleClicked on Keyguard, mDozingOnDown: false");
                     // Try triggering face auth, this "might" run. Check
                     // KeyguardUpdateMonitor#shouldListenForFace to see when face auth won't run.
+                    mKeyguardFaceAuthInteractor.onNotificationPanelClicked();
                     boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth(
                             FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED);
 
@@ -3333,7 +3338,10 @@
 
         mGestureRecorder = recorder;
         mHideExpandedRunnable = hideExpandedRunnable;
-        mNotificationStackScrollLayoutController.setShelfController(notificationShelfController);
+        if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
+            mNotificationStackScrollLayoutController.setShelfController(
+                    notificationShelfController);
+        }
         mNotificationShelfController = notificationShelfController;
         mLockscreenShadeTransitionController.bindController(notificationShelfController);
         updateMaxDisplayedNotifications(true);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 7cb1cbe..ef14d1c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -64,6 +64,7 @@
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.media.controls.pipeline.MediaDataManager;
 import com.android.systemui.media.controls.ui.MediaHierarchyManager;
 import com.android.systemui.plugins.FalsingManager;
@@ -132,6 +133,7 @@
     private final FalsingCollector mFalsingCollector;
     private final LockscreenGestureLogger mLockscreenGestureLogger;
     private final ShadeLogger mShadeLog;
+    private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
     private final FeatureFlags mFeatureFlags;
     private final InteractionJankMonitor mInteractionJankMonitor;
     private final FalsingManager mFalsingManager;
@@ -318,7 +320,8 @@
             MetricsLogger metricsLogger,
             FeatureFlags featureFlags,
             InteractionJankMonitor interactionJankMonitor,
-            ShadeLogger shadeLog
+            ShadeLogger shadeLog,
+            KeyguardFaceAuthInteractor keyguardFaceAuthInteractor
     ) {
         mPanelViewControllerLazy = panelViewControllerLazy;
         mPanelView = panelView;
@@ -357,6 +360,7 @@
         mLockscreenGestureLogger = lockscreenGestureLogger;
         mMetricsLogger = metricsLogger;
         mShadeLog = shadeLog;
+        mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
         mFeatureFlags = featureFlags;
         mInteractionJankMonitor = interactionJankMonitor;
 
@@ -937,6 +941,7 @@
         // When expanding QS, let's authenticate the user if possible,
         // this will speed up notification actions.
         if (height == 0 && !mKeyguardStateController.canDismissLockScreen()) {
+            mKeyguardFaceAuthInteractor.onQsExpansionStared();
             mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.QS_EXPANDED);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 9b1e2fa..142689e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -1423,7 +1423,7 @@
 
     private boolean canUnlockWithFingerprint() {
         return mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                getCurrentUser());
+                getCurrentUser()) && mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed();
     }
 
     private void showErrorMessageNowOrLater(String errString, @Nullable String followUpMsg) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index e6715a1..7eb63da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -24,6 +24,7 @@
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.IndentingPrintWriter;
+import android.util.Log;
 import android.util.MathUtils;
 import android.view.View;
 import android.view.ViewGroup;
@@ -52,6 +53,8 @@
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
+import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
 import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -96,6 +99,11 @@
     private NotificationShelfController mController;
     private float mActualWidth = -1;
     private boolean mSensitiveRevealAnimEndabled;
+    private boolean mShelfRefactorFlagEnabled;
+    private boolean mCanModifyColorOfNotifications;
+    private boolean mCanInteract;
+    private NotificationStackScrollLayout mHostLayout;
+    private NotificationRoundnessManager mRoundnessManager;
 
     public NotificationShelf(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -132,6 +140,7 @@
 
     public void bind(AmbientState ambientState,
                      NotificationStackScrollLayoutController hostLayoutController) {
+        assertRefactorFlagDisabled();
         mAmbientState = ambientState;
         mHostLayoutController = hostLayoutController;
         hostLayoutController.setOnNotificationRemovedListener((child, isTransferInProgress) -> {
@@ -139,6 +148,14 @@
         });
     }
 
+    public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout,
+            NotificationRoundnessManager roundnessManager) {
+        if (!checkRefactorFlagEnabled()) return;
+        mAmbientState = ambientState;
+        mHostLayout = hostLayout;
+        mRoundnessManager = roundnessManager;
+    }
+
     private void updateResources() {
         Resources res = getResources();
         mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
@@ -230,7 +247,7 @@
             } else {
                 viewState.setAlpha(1f - ambientState.getHideAmount());
             }
-            viewState.belowSpeedBump = mHostLayoutController.getSpeedBumpIndex() == 0;
+            viewState.belowSpeedBump = getSpeedBumpIndex() == 0;
             viewState.hideSensitive = false;
             viewState.setXTranslation(getTranslationX());
             viewState.hasItemsInStableShelf = lastViewState.inShelf;
@@ -273,6 +290,14 @@
         }
     }
 
+    private int getSpeedBumpIndex() {
+        if (mShelfRefactorFlagEnabled) {
+            return mHostLayout.getSpeedBumpIndex();
+        } else {
+            return mHostLayoutController.getSpeedBumpIndex();
+        }
+    }
+
     /**
      * @param fractionToShade Fraction of lockscreen to shade transition
      * @param shortestWidth   Shortest width to use for lockscreen shelf
@@ -385,8 +410,8 @@
         int baseZHeight = mAmbientState.getBaseZHeight();
         int clipTopAmount = 0;
 
-        for (int i = 0; i < mHostLayoutController.getChildCount(); i++) {
-            ExpandableView child = mHostLayoutController.getChildAt(i);
+        for (int i = 0; i < getHostLayoutChildCount(); i++) {
+            ExpandableView child = getHostLayoutChildAt(i);
             if (!child.needsClippingToShelf() || child.getVisibility() == GONE) {
                 continue;
             }
@@ -425,7 +450,7 @@
                     transitionAmount = inShelfAmount;
                 }
                 // We don't want to modify the color if the notification is hun'd
-                if (isLastChild && mController.canModifyColorOfNotifications()) {
+                if (isLastChild && canModifyColorOfNotifications()) {
                     if (colorOfViewBeforeLast == NO_COLOR) {
                         colorOfViewBeforeLast = ownColorUntinted;
                     }
@@ -471,11 +496,11 @@
 
         // TODO(b/172289889) transition last icon in shelf to notification icon and vice versa.
         setVisibility(isHidden ? View.INVISIBLE : View.VISIBLE);
-        mShelfIcons.setSpeedBumpIndex(mHostLayoutController.getSpeedBumpIndex());
+        mShelfIcons.setSpeedBumpIndex(getSpeedBumpIndex());
         mShelfIcons.calculateIconXTranslations();
         mShelfIcons.applyIconStates();
-        for (int i = 0; i < mHostLayoutController.getChildCount(); i++) {
-            View child = mHostLayoutController.getChildAt(i);
+        for (int i = 0; i < getHostLayoutChildCount(); i++) {
+            View child = getHostLayoutChildAt(i);
             if (!(child instanceof ExpandableNotificationRow)
                     || child.getVisibility() == GONE) {
                 continue;
@@ -490,6 +515,30 @@
         }
     }
 
+    private ExpandableView getHostLayoutChildAt(int index) {
+        if (mShelfRefactorFlagEnabled) {
+            return (ExpandableView) mHostLayout.getChildAt(index);
+        } else {
+            return mHostLayoutController.getChildAt(index);
+        }
+    }
+
+    private int getHostLayoutChildCount() {
+        if (mShelfRefactorFlagEnabled) {
+            return mHostLayout.getChildCount();
+        } else {
+            return mHostLayoutController.getChildCount();
+        }
+    }
+
+    private boolean canModifyColorOfNotifications() {
+        if (mShelfRefactorFlagEnabled) {
+            return mCanModifyColorOfNotifications && mAmbientState.isShadeExpanded();
+        } else {
+            return mController.canModifyColorOfNotifications();
+        }
+    }
+
     private void updateCornerRoundnessOnScroll(
             ActivatableNotificationView anv,
             float viewStart,
@@ -504,7 +553,7 @@
                 && anv == mAmbientState.getTrackedHeadsUpRow();
 
         final boolean shouldUpdateCornerRoundness = viewStart < shelfStart
-                && !mHostLayoutController.isViewAffectedBySwipe(anv)
+                && !isViewAffectedBySwipe(anv)
                 && !isUnlockedHeadsUp
                 && !isHunGoingToShade
                 && !anv.isAboveShelf()
@@ -556,6 +605,14 @@
         anv.requestBottomRoundness(bottomValue, sourceType, /* animate = */ false);
     }
 
+    private boolean isViewAffectedBySwipe(ExpandableView expandableView) {
+        if (!mShelfRefactorFlagEnabled) {
+            return mHostLayoutController.isViewAffectedBySwipe(expandableView);
+        } else {
+            return mRoundnessManager.isViewAffectedBySwipe(expandableView);
+        }
+    }
+
     /**
      * Clips transient views to the top of the shelf - Transient views are only used for
      * disappearing views/animations and need to be clipped correctly by the shelf to ensure they
@@ -563,8 +620,8 @@
      * swipes quickly.
      */
     private void clipTransientViews() {
-        for (int i = 0; i < mHostLayoutController.getTransientViewCount(); i++) {
-            View transientView = mHostLayoutController.getTransientView(i);
+        for (int i = 0; i < getHostLayoutTransientViewCount(); i++) {
+            View transientView = getHostLayoutTransientView(i);
             if (transientView instanceof ExpandableView) {
                 ExpandableView transientExpandableView = (ExpandableView) transientView;
                 updateNotificationClipHeight(transientExpandableView, getTranslationY(), -1);
@@ -572,6 +629,22 @@
         }
     }
 
+    private View getHostLayoutTransientView(int index) {
+        if (mShelfRefactorFlagEnabled) {
+            return mHostLayout.getTransientView(index);
+        } else {
+            return mHostLayoutController.getTransientView(index);
+        }
+    }
+
+    private int getHostLayoutTransientViewCount() {
+        if (mShelfRefactorFlagEnabled) {
+            return mHostLayout.getTransientViewCount();
+        } else {
+            return mHostLayoutController.getTransientViewCount();
+        }
+    }
+
     private void updateIconClipAmount(ExpandableNotificationRow row) {
         float maxTop = row.getTranslationY();
         if (getClipTopAmount() != 0) {
@@ -911,18 +984,27 @@
 
     @Override
     public void onStateChanged(int newState) {
+        assertRefactorFlagDisabled();
         mStatusBarState = newState;
         updateInteractiveness();
     }
 
     private void updateInteractiveness() {
-        mInteractive = mStatusBarState == StatusBarState.KEYGUARD && mHasItemsInStableShelf;
+        mInteractive = canInteract() && mHasItemsInStableShelf;
         setClickable(mInteractive);
         setFocusable(mInteractive);
         setImportantForAccessibility(mInteractive ? View.IMPORTANT_FOR_ACCESSIBILITY_YES
                 : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
     }
 
+    private boolean canInteract() {
+        if (mShelfRefactorFlagEnabled) {
+            return mCanInteract;
+        } else {
+            return mStatusBarState == StatusBarState.KEYGUARD;
+        }
+    }
+
     @Override
     protected boolean isInteractive() {
         return mInteractive;
@@ -959,12 +1041,46 @@
         return false;
     }
 
+    private void assertRefactorFlagDisabled() {
+        if (mShelfRefactorFlagEnabled) {
+            NotificationShelfController.throwIllegalFlagStateError(false);
+        }
+    }
+
+    private boolean checkRefactorFlagEnabled() {
+        if (!mShelfRefactorFlagEnabled) {
+            Log.wtf(TAG,
+                    "Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is disabled.");
+        }
+        return mShelfRefactorFlagEnabled;
+    }
+
     public void setController(NotificationShelfController notificationShelfController) {
+        assertRefactorFlagDisabled();
         mController = notificationShelfController;
     }
 
+    public void setCanModifyColorOfNotifications(boolean canModifyColorOfNotifications) {
+        if (!checkRefactorFlagEnabled()) return;
+        mCanModifyColorOfNotifications = canModifyColorOfNotifications;
+    }
+
+    public void setCanInteract(boolean canInteract) {
+        if (!checkRefactorFlagEnabled()) return;
+        mCanInteract = canInteract;
+        updateInteractiveness();
+    }
+
     public void setIndexOfFirstViewInShelf(ExpandableView firstViewInShelf) {
-        mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf);
+        mIndexOfFirstViewInShelf = getIndexOfViewInHostLayout(firstViewInShelf);
+    }
+
+    private int getIndexOfViewInHostLayout(ExpandableView child) {
+        if (mShelfRefactorFlagEnabled) {
+            return mHostLayout.indexOfChild(child);
+        } else {
+            return mHostLayoutController.indexOfChild(child);
+        }
     }
 
     /**
@@ -975,6 +1091,15 @@
         mSensitiveRevealAnimEndabled = enabled;
     }
 
+    public void setRefactorFlagEnabled(boolean enabled) {
+        mShelfRefactorFlagEnabled = enabled;
+    }
+
+    public void requestRoundnessResetFor(ExpandableView child) {
+        if (!checkRefactorFlagEnabled()) return;
+        child.requestRoundnessReset(SHELF_SCROLL);
+    }
+
     /**
      * This method resets the OnScroll roundness of a view to 0f
      * <p>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt
index bf3d47c..07cfd0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt
@@ -16,8 +16,11 @@
 
 package com.android.systemui.statusbar
 
+import android.util.Log
 import android.view.View
 import android.view.View.OnClickListener
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView.OnActivatedListener
 import com.android.systemui.statusbar.notification.row.ExpandableView
@@ -51,4 +54,29 @@
 
     /** @see View.setOnClickListener */
     fun setOnClickListener(listener: OnClickListener)
+
+    companion object {
+        @JvmStatic
+        fun assertRefactorFlagDisabled(featureFlags: FeatureFlags) {
+            if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
+                throwIllegalFlagStateError(expected = false)
+            }
+        }
+
+        @JvmStatic
+        fun checkRefactorFlagEnabled(featureFlags: FeatureFlags): Boolean =
+            featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR).also { enabled ->
+                if (!enabled) {
+                    Log.wtf("NotifShelf", getErrorMessage(expected = true))
+                }
+            }
+
+        @JvmStatic
+        fun throwIllegalFlagStateError(expected: Boolean): Nothing =
+            error(getErrorMessage(expected))
+
+        private fun getErrorMessage(expected: Boolean): String =
+            "Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is " +
+                if (expected) "disabled" else "enabled"
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
index ecd0c41..fcff437 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
@@ -48,6 +48,10 @@
             View.SCALE_Y, R.id.scale_y_animator_tag, R.id.scale_y_animator_start_value_tag,
             R.id.scale_y_animator_end_value_tag);
 
+    public static final AnimatableProperty ALPHA = AnimatableProperty.from(
+            View.ALPHA, R.id.alpha_animator_tag, R.id.alpha_animator_start_value_tag,
+            R.id.alpha_animator_end_value_tag);
+
     /**
      * Similar to X, however this doesn't allow for any other modifications other than from this
      * property. When using X, it's possible that the view is laid out during the animation,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
index 82bd45c..6322edf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -24,6 +24,10 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.expansionChanges
@@ -40,22 +44,26 @@
 import com.android.systemui.statusbar.policy.headsUpEvents
 import com.android.systemui.util.settings.SecureSettings
 import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
-import javax.inject.Inject
-import kotlin.time.Duration
-import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emitAll
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.transformLatest
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.yield
+import javax.inject.Inject
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.seconds
 
 /**
  * Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section
@@ -69,6 +77,7 @@
     private val headsUpManager: HeadsUpManager,
     private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider,
     private val keyguardRepository: KeyguardRepository,
+    private val keyguardTransitionRepository: KeyguardTransitionRepository,
     private val notifPipelineFlags: NotifPipelineFlags,
     @Application private val scope: CoroutineScope,
     private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
@@ -99,21 +108,46 @@
     }
 
     private suspend fun trackUnseenNotificationsWhileUnlocked() {
+        // Whether or not we're actively tracking unseen notifications to mark them as seen when
+        // appropriate.
+        val isTrackingUnseen: Flow<Boolean> =
+            keyguardRepository.isKeyguardShowing
+                // transformLatest so that we can cancel listening to keyguard transitions once
+                // isKeyguardShowing changes (after a successful transition to the keyguard).
+                .transformLatest { isShowing ->
+                    if (isShowing) {
+                        // If the keyguard is showing, we're not tracking unseen.
+                        emit(false)
+                    } else {
+                        // If the keyguard stops showing, then start tracking unseen notifications.
+                        emit(true)
+                        // If the screen is turning off, stop tracking, but if that transition is
+                        // cancelled, then start again.
+                        emitAll(
+                            keyguardTransitionRepository.transitions
+                                .map { step -> !step.isScreenTurningOff }
+                        )
+                    }
+                }
+                // Prevent double emit of `false` caused by transition to AOD, followed by keyguard
+                // showing
+                .distinctUntilChanged()
+
         // Use collectLatest so that trackUnseenNotifications() is cancelled when the keyguard is
         // showing again
-        var clearUnseenOnUnlock = false
-        keyguardRepository.isKeyguardShowing.collectLatest { isKeyguardShowing ->
-            if (isKeyguardShowing) {
+        var clearUnseenOnBeginTracking = false
+        isTrackingUnseen.collectLatest { trackingUnseen ->
+            if (!trackingUnseen) {
                 // Wait for the user to spend enough time on the lock screen before clearing unseen
                 // set when unlocked
                 awaitTimeSpentNotDozing(SEEN_TIMEOUT)
-                clearUnseenOnUnlock = true
+                clearUnseenOnBeginTracking = true
             } else {
-                unseenNotifFilter.invalidateList("keyguard no longer showing")
-                if (clearUnseenOnUnlock) {
-                    clearUnseenOnUnlock = false
+                if (clearUnseenOnBeginTracking) {
+                    clearUnseenOnBeginTracking = false
                     unseenNotifications.clear()
                 }
+                unseenNotifFilter.invalidateList("keyguard no longer showing")
                 trackUnseenNotifications()
             }
         }
@@ -142,7 +176,10 @@
     }
 
     private suspend fun clearUnseenNotificationsWhenShadeIsExpanded() {
-        statusBarStateController.expansionChanges.collect { isExpanded ->
+        statusBarStateController.expansionChanges.collectLatest { isExpanded ->
+            // Give keyguard events time to propagate, in case this expansion is part of the
+            // keyguard transition and not the user expanding the shade
+            yield()
             if (isExpanded) {
                 unseenNotifications.clear()
             }
@@ -276,3 +313,6 @@
         private val SEEN_TIMEOUT = 5.seconds
     }
 }
+
+private val TransitionStep.isScreenTurningOff: Boolean get() =
+    transitionState == TransitionState.STARTED && to != KeyguardState.GONE
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index a9d1255..950ab5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -299,6 +299,16 @@
      */
     private boolean mShowGroupBackgroundWhenExpanded;
 
+    /**
+     * True if we always show the collapsed layout on lockscreen because vertical space is low.
+     */
+    private boolean mSaveSpaceOnLockscreen;
+
+    /**
+     * True if we use intrinsic height regardless of vertical space available on lockscreen.
+     */
+    private boolean mIgnoreLockscreenConstraints;
+
     private OnClickListener mExpandClickListener = new OnClickListener() {
         @Override
         public void onClick(View v) {
@@ -394,6 +404,14 @@
         return mGroupExpansionChanging;
     }
 
+    public void setSaveSpaceOnLockscreen(boolean saveSpace) {
+        mSaveSpaceOnLockscreen = saveSpace;
+    }
+
+    public boolean getSaveSpaceOnLockscreen() {
+        return mSaveSpaceOnLockscreen;
+    }
+
     public void setGroupExpansionChanging(boolean changing) {
         mGroupExpansionChanging = changing;
     }
@@ -419,15 +437,9 @@
      */
     public void setAnimationRunning(boolean running) {
         // Sets animations running in the private/public layouts.
-        if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ANIMATE_BIG_PICTURE)) {
-            for (NotificationContentView l : mLayouts) {
-                if (l != null) {
-                    l.setContentAnimationRunning(running);
-                    setIconAnimationRunning(running, l);
-                }
-            }
-        } else {
-            for (NotificationContentView l : mLayouts) {
+        for (NotificationContentView l : mLayouts) {
+            if (l != null) {
+                l.setContentAnimationRunning(running);
                 setIconAnimationRunning(running, l);
             }
         }
@@ -2553,11 +2565,18 @@
     }
 
     @Override
+    public int getHeightWithoutLockscreenConstraints() {
+        mIgnoreLockscreenConstraints = true;
+        final int height = getIntrinsicHeight();
+        mIgnoreLockscreenConstraints = false;
+        return height;
+    }
+
+    @Override
     public int getIntrinsicHeight() {
         if (isUserLocked()) {
             return getActualHeight();
-        }
-        if (mGuts != null && mGuts.isExposed()) {
+        } else if (mGuts != null && mGuts.isExposed()) {
             return mGuts.getIntrinsicHeight();
         } else if ((isChildInGroup() && !isGroupExpanded())) {
             return mPrivateLayout.getMinHeight();
@@ -2579,13 +2598,14 @@
             return getCollapsedHeight();
         }
     }
-
     /**
      * @return {@code true} if the notification can show it's heads up layout. This is mostly true
      * except for legacy use cases.
      */
     public boolean canShowHeadsUp() {
-        if (mOnKeyguard && !isDozing() && !isBypassEnabled() && !mEntry.isStickyAndNotDemoted()) {
+        if (mOnKeyguard && !isDozing() && !isBypassEnabled() &&
+                (!mEntry.isStickyAndNotDemoted()
+                        || (!mIgnoreLockscreenConstraints && mSaveSpaceOnLockscreen))) {
             return false;
         }
         return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 9df6ba9..5edff5f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -291,6 +291,11 @@
             long duration) {
     }
 
+    public int getHeightWithoutLockscreenConstraints() {
+        // ExpandableNotificationRow overrides this.
+        return getHeight();
+    }
+
     /**
      * @return The desired notification height.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
new file mode 100644
index 0000000..8ba65f7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.shelf.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+/** Interactor for the [NotificationShelf] */
+@CentralSurfacesComponent.CentralSurfacesScope
+class NotificationShelfInteractor
+@Inject
+constructor(
+    private val keyguardRepository: KeyguardRepository,
+    private val deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository,
+) {
+    /** Is the shelf showing on the keyguard? */
+    val isShowingOnKeyguard: Flow<Boolean>
+        get() = keyguardRepository.isKeyguardShowing
+
+    /** Is the system in a state where the shelf is just a static display of notification icons? */
+    val isShelfStatic: Flow<Boolean>
+        get() =
+            combine(
+                keyguardRepository.isKeyguardShowing,
+                deviceEntryFaceAuthRepository.isBypassEnabled,
+            ) { isKeyguardShowing, isBypassEnabled ->
+                isKeyguardShowing && isBypassEnabled
+            }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/view/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
similarity index 64%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/view/NotificationShelfViewBinder.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
index a235157..b190cf6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/view/NotificationShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
@@ -14,37 +14,35 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.shelf.view
+package com.android.systemui.statusbar.notification.shelf.ui.viewbinder
 
 import android.view.View
-import android.view.View.OnAttachStateChangeListener
 import android.view.accessibility.AccessibilityManager
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl
 import com.android.systemui.statusbar.NotificationShelf
 import com.android.systemui.statusbar.NotificationShelfController
-import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController
 import com.android.systemui.statusbar.notification.row.ExpandableOutlineViewController
 import com.android.systemui.statusbar.notification.row.ExpandableViewController
+import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
 import com.android.systemui.statusbar.notification.stack.AmbientState
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
-import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.phone.NotificationIconContainer
 import com.android.systemui.statusbar.phone.NotificationTapHelper
 import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
-import dagger.Binds
-import dagger.Module
+import com.android.systemui.util.kotlin.getValue
+import dagger.Lazy
 import javax.inject.Inject
-
-/** Binds a [NotificationShelf] to its backend. */
-interface NotificationShelfViewBinder {
-    fun bind(shelf: NotificationShelf)
-}
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
 
 /**
  * Controller class for [NotificationShelf]. This implementation serves as a temporary wrapper
@@ -57,30 +55,30 @@
 @Inject
 constructor(
     private val shelf: NotificationShelf,
-    private val viewBinder: NotificationShelfViewBinder,
-    private val keyguardBypassController: KeyguardBypassController,
+    private val viewModel: NotificationShelfViewModel,
     featureFlags: FeatureFlags,
     private val notifTapHelperFactory: NotificationTapHelper.Factory,
     private val a11yManager: AccessibilityManager,
     private val falsingManager: FalsingManager,
     private val falsingCollector: FalsingCollector,
-    private val statusBarStateController: SysuiStatusBarStateController,
+    hostControllerLazy: Lazy<NotificationStackScrollLayoutController>,
 ) : NotificationShelfController {
 
-    private var ambientState: AmbientState? = null
+    private val hostController: NotificationStackScrollLayoutController by hostControllerLazy
 
     override val view: NotificationShelf
-        get() = shelf
+        get() = unsupported
 
     init {
         shelf.apply {
+            setRefactorFlagEnabled(featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR))
             useRoundnessSourceTypes(featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES))
             setSensitiveRevealAnimEndabled(featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM))
         }
     }
 
     fun init() {
-        viewBinder.bind(shelf)
+        NotificationShelfViewBinder.bind(viewModel, shelf)
 
         ActivatableNotificationViewController(
                 shelf,
@@ -91,23 +89,9 @@
                 falsingCollector,
             )
             .init()
-        shelf.setController(this)
-        val onAttachStateListener =
-            object : OnAttachStateChangeListener {
-                override fun onViewAttachedToWindow(v: View) {
-                    statusBarStateController.addCallback(
-                        shelf,
-                        SysuiStatusBarStateController.RANK_SHELF,
-                    )
-                }
-
-                override fun onViewDetachedFromWindow(v: View) {
-                    statusBarStateController.removeCallback(shelf)
-                }
-            }
-        shelf.addOnAttachStateChangeListener(onAttachStateListener)
-        if (shelf.isAttachedToWindow) {
-            onAttachStateListener.onViewAttachedToWindow(shelf)
+        hostController.setShelf(shelf)
+        hostController.setOnNotificationRemovedListener { child, _ ->
+            view.requestRoundnessResetFor(child)
         }
     }
 
@@ -117,10 +101,7 @@
     override val shelfIcons: NotificationIconContainer
         get() = shelf.shelfIcons
 
-    override fun canModifyColorOfNotifications(): Boolean {
-        return (ambientState?.isShadeExpanded == true &&
-            !(ambientState?.isOnKeyguard == true && keyguardBypassController.bypassEnabled))
-    }
+    override fun canModifyColorOfNotifications(): Boolean = unsupported
 
     override fun setOnActivatedListener(listener: ActivatableNotificationView.OnActivatedListener) {
         shelf.setOnActivatedListener(listener)
@@ -128,25 +109,27 @@
 
     override fun bind(
         ambientState: AmbientState,
-        notificationStackScrollLayoutController: NotificationStackScrollLayoutController
-    ) {
-        shelf.bind(ambientState, notificationStackScrollLayoutController)
-        this.ambientState = ambientState
-    }
+        notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
+    ) = unsupported
 
     override fun setOnClickListener(listener: View.OnClickListener) {
         shelf.setOnClickListener(listener)
     }
+
+    private val unsupported: Nothing
+        get() = NotificationShelfController.throwIllegalFlagStateError(expected = true)
 }
 
-@Module(includes = [PrivateShelfViewBinderModule::class]) object NotificationShelfViewBinderModule
-
-@Module
-private interface PrivateShelfViewBinderModule {
-    @Binds fun bindImpl(impl: NotificationShelfViewBinderImpl): NotificationShelfViewBinder
-}
-
-@CentralSurfacesScope
-private class NotificationShelfViewBinderImpl @Inject constructor() : NotificationShelfViewBinder {
-    override fun bind(shelf: NotificationShelf) {}
+/** Binds a [NotificationShelf] to its backend. */
+object NotificationShelfViewBinder {
+    fun bind(viewModel: NotificationShelfViewModel, shelf: NotificationShelf) {
+        shelf.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                viewModel.canModifyColorOfNotifications
+                    .onEach(shelf::setCanModifyColorOfNotifications)
+                    .launchIn(this)
+                viewModel.isClickable.onEach(shelf::setCanInteract).launchIn(this)
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
new file mode 100644
index 0000000..5e297c8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.shelf.ui.viewmodel
+
+import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** ViewModel for [NotificationShelf]. */
+@CentralSurfacesScope
+class NotificationShelfViewModel
+@Inject
+constructor(
+    private val interactor: NotificationShelfInteractor,
+) {
+    /** Is the shelf allowed to be clickable when it has content? */
+    val isClickable: Flow<Boolean>
+        get() = interactor.isShowingOnKeyguard
+
+    /** Is the shelf allowed to modify the color of notifications in the host layout? */
+    val canModifyColorOfNotifications: Flow<Boolean>
+        get() = interactor.isShelfStatic.map { static -> !static }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java
index 112d48c..00b9aa4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java
@@ -32,6 +32,7 @@
     public long duration;
     public long delay;
     private ArrayMap<Property, Interpolator> mInterpolatorMap;
+    private Consumer<Property> mAnimationCancelAction;
     private Consumer<Property> mAnimationEndAction;
 
     /**
@@ -50,27 +51,43 @@
      * @return a listener that will be added for a given property during its animation.
      */
     public AnimatorListenerAdapter getAnimationFinishListener(Property property) {
-        if (mAnimationEndAction == null) {
+        if (mAnimationEndAction == null && mAnimationCancelAction == null) {
             return null;
         }
+        Consumer<Property> cancelAction = mAnimationCancelAction;
         Consumer<Property> endAction = mAnimationEndAction;
         return new AnimatorListenerAdapter() {
             private boolean mCancelled;
 
             @Override
             public void onAnimationCancel(Animator animation) {
-                 mCancelled = true;
+                mCancelled = true;
+                if (cancelAction != null) {
+                    cancelAction.accept(property);
+                }
             }
 
             @Override
             public void onAnimationEnd(Animator animation) {
-                if (!mCancelled) {
+                if (!mCancelled && endAction != null) {
                     endAction.accept(property);
                 }
             }
         };
     }
 
+    /**
+     * Add a callback for animation cancellation.
+     */
+    public AnimationProperties setAnimationCancelAction(Consumer<Property> listener) {
+        mAnimationCancelAction = listener;
+        return this;
+    }
+
+    /**
+     * Add a callback for animation ending successfully. The callback will not be called when the
+     * animations is cancelled.
+     */
     public AnimationProperties setAnimationEndAction(Consumer<Property> listener) {
         mAnimationEndAction = listener;
         return this;
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 e47e414..af608a7 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
@@ -5137,8 +5137,26 @@
         requestChildrenUpdate();
     }
 
+    public void setShelf(NotificationShelf shelf) {
+        if (!NotificationShelfController.checkRefactorFlagEnabled(
+                mAmbientState.getFeatureFlags())) {
+            return;
+        }
+        int index = -1;
+        if (mShelf != null) {
+            index = indexOfChild(mShelf);
+            removeView(mShelf);
+        }
+        mShelf = shelf;
+        addView(mShelf, index);
+        mAmbientState.setShelf(mShelf);
+        mStateAnimator.setShelf(mShelf);
+        shelf.bind(mAmbientState, this, mController.getNotificationRoundnessManager());
+    }
+
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setShelfController(NotificationShelfController notificationShelfController) {
+        NotificationShelfController.assertRefactorFlagDisabled(mAmbientState.getFeatureFlags());
         int index = -1;
         if (mShelf != null) {
             index = indexOfChild(mShelf);
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 792746c..1c8727f 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
@@ -73,6 +73,7 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarState;
@@ -1382,6 +1383,7 @@
     }
 
     public void setShelfController(NotificationShelfController notificationShelfController) {
+        NotificationShelfController.assertRefactorFlagDisabled(mFeatureFlags);
         mView.setShelfController(notificationShelfController);
     }
 
@@ -1593,6 +1595,11 @@
         mView.setOnNotificationRemovedListener(listener);
     }
 
+    public void setShelf(NotificationShelf shelf) {
+        if (!NotificationShelfController.checkRefactorFlagEnabled(mFeatureFlags)) return;
+        mView.setShelf(shelf);
+    }
+
     /**
      * Enum for UiEvent logged from this class
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
index 0922428..c7cb70c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
@@ -23,12 +23,14 @@
 import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.media.controls.pipeline.MediaDataManager
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.StatusBarState.KEYGUARD
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
 import com.android.systemui.util.Compile
+import com.android.systemui.util.LargeScreenUtils.shouldUseSplitNotificationShade
 import com.android.systemui.util.children
 import java.io.PrintWriter
 import javax.inject.Inject
@@ -51,11 +53,10 @@
 constructor(
     private val statusBarStateController: SysuiStatusBarStateController,
     private val lockscreenShadeTransitionController: LockscreenShadeTransitionController,
+    private val mediaDataManager: MediaDataManager,
     @Main private val resources: Resources
 ) {
 
-    private lateinit var lastComputeHeightLog : String
-
     /**
      * Maximum # notifications to show on Keyguard; extras will be collapsed in an overflow shelf.
      * If there are exactly 1 + mMaxKeyguardNotifications, and they fit in the available space
@@ -67,68 +68,158 @@
     /** Minimum space between two notifications, see [calculateGapAndDividerHeight]. */
     private var dividerHeight by notNull<Float>()
 
+    /**
+     * True when there is not enough vertical space to show at least one notification with heads up
+     * layout. When true, notifications always show collapsed layout.
+     */
+    private var saveSpaceOnLockscreen = false
+
     init {
         updateResources()
     }
 
     /**
      * Returns whether notifications and (shelf if visible) can fit in total space available.
-     * [spaceForShelf] is extra vertical space allowed for the shelf to overlap the lock icon.
+     * [shelfSpace] is extra vertical space allowed for the shelf to overlap the lock icon.
      */
     private fun canStackFitInSpace(
         stackHeight: StackHeight,
-        spaceForNotifications: Float,
-        spaceForShelf: Float,
-    ): Boolean {
-
-        val (notificationsHeight, shelfHeightWithSpaceBefore) = stackHeight
-        var canFit: Boolean
+        notifSpace: Float,
+        shelfSpace: Float,
+    ): FitResult {
+        val (notifHeight, notifHeightSaveSpace, shelfHeightWithSpaceBefore) = stackHeight
 
         if (shelfHeightWithSpaceBefore == 0f) {
-            canFit = notificationsHeight <= spaceForNotifications
-            log {
-                "canStackFitInSpace[$canFit] = notificationsHeight[$notificationsHeight]" +
-                    " <= spaceForNotifications[$spaceForNotifications]"
+            if (notifHeight <= notifSpace) {
+                log {
+                    "\tcanStackFitInSpace[FIT] = notifHeight[$notifHeight]" +
+                        " <= notifSpace[$notifSpace]"
+                }
+                return FitResult.FIT
             }
-        } else {
-            canFit =
-                (notificationsHeight + shelfHeightWithSpaceBefore) <=
-                    (spaceForNotifications + spaceForShelf)
+            if (notifHeightSaveSpace <= notifSpace) {
+                log {
+                    "\tcanStackFitInSpace[FIT_IF_SAVE_SPACE]" +
+                        " = notifHeightSaveSpace[$notifHeightSaveSpace]" +
+                        " <= notifSpace[$notifSpace]"
+                }
+                return FitResult.FIT_IF_SAVE_SPACE
+            }
             log {
-                "canStackFitInSpace[$canFit] = (notificationsHeight[$notificationsHeight]" +
-                    " + shelfHeightWithSpaceBefore[$shelfHeightWithSpaceBefore])" +
-                    " <= (spaceForNotifications[$spaceForNotifications] " +
-                    " + spaceForShelf[$spaceForShelf])"
+                "\tcanStackFitInSpace[NO_FIT]" +
+                    " = notifHeightSaveSpace[$notifHeightSaveSpace] > notifSpace[$notifSpace]"
+            }
+            return FitResult.NO_FIT
+        } else {
+            if ((notifHeight + shelfHeightWithSpaceBefore) <= (notifSpace + shelfSpace)) {
+                log {
+                    "\tcanStackFitInSpace[FIT] = (notifHeight[$notifHeight]" +
+                        " + shelfHeightWithSpaceBefore[$shelfHeightWithSpaceBefore])" +
+                        " <= (notifSpace[$notifSpace] " +
+                        " + spaceForShelf[$shelfSpace])"
+                }
+                return FitResult.FIT
+            } else if (
+                (notifHeightSaveSpace + shelfHeightWithSpaceBefore) <= (notifSpace + shelfSpace)
+            ) {
+                log {
+                    "\tcanStackFitInSpace[FIT_IF_SAVE_SPACE]" +
+                        " = (notifHeightSaveSpace[$notifHeightSaveSpace]" +
+                        " + shelfHeightWithSpaceBefore[$shelfHeightWithSpaceBefore])" +
+                        " <= (notifSpace[$notifSpace] + shelfSpace[$shelfSpace])"
+                }
+                return FitResult.FIT_IF_SAVE_SPACE
+            } else {
+                log {
+                    "\tcanStackFitInSpace[NO_FIT]" +
+                        " = (notifHeightSaveSpace[$notifHeightSaveSpace]" +
+                        " + shelfHeightWithSpaceBefore[$shelfHeightWithSpaceBefore])" +
+                        " > (notifSpace[$notifSpace] + shelfSpace[$shelfSpace])"
+                }
+                return FitResult.NO_FIT
             }
         }
-        return canFit
     }
 
     /**
-     * Given the [spaceForNotifications] and [spaceForShelf] constraints, calculate how many
-     * notifications to show. This number is only valid in keyguard.
+     * Given the [notifSpace] and [shelfSpace] constraints, calculate how many notifications to
+     * show. This number is only valid in keyguard.
      *
      * @param totalAvailableSpace space for notifications. This includes the space for the shelf.
      */
     fun computeMaxKeyguardNotifications(
         stack: NotificationStackScrollLayout,
-        spaceForNotifications: Float,
-        spaceForShelf: Float,
-        shelfIntrinsicHeight: Float
+        notifSpace: Float,
+        shelfSpace: Float,
+        shelfHeight: Float,
     ): Int {
+        log { "\n " }
+        log {
+            "computeMaxKeyguardNotifications ---" +
+                "\n\tnotifSpace $notifSpace" +
+                "\n\tspaceForShelf $shelfSpace" +
+                "\n\tshelfIntrinsicHeight $shelfHeight"
+        }
+        if (notifSpace + shelfSpace <= 0f) {
+            log { "--- No space to show anything. maxNotifs=0" }
+            return 0
+        }
         log { "\n" }
 
-        val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight,
-            /* computeHeight= */ false)
+        val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfHeight)
+        val isMediaShowing = mediaDataManager.hasActiveMediaOrRecommendation()
 
-        var maxNotifications =
+        log { "\tGet maxNotifWithoutSavingSpace ---" }
+        val maxNotifWithoutSavingSpace =
             stackHeightSequence.lastIndexWhile { heightResult ->
                 canStackFitInSpace(
                     heightResult,
-                    spaceForNotifications = spaceForNotifications,
-                    spaceForShelf = spaceForShelf)
+                    notifSpace = notifSpace,
+                    shelfSpace = shelfSpace
+                ) == FitResult.FIT
             }
 
+        // How many notifications we can show at heightWithoutLockscreenConstraints
+        var minCountAtHeightWithoutConstraints =
+            if (isMediaShowing && !shouldUseSplitNotificationShade(resources)) 2 else 1
+        log {
+            "\t---maxNotifWithoutSavingSpace=$maxNotifWithoutSavingSpace " +
+                "isMediaShowing=$isMediaShowing" +
+                "minCountAtHeightWithoutConstraints=$minCountAtHeightWithoutConstraints"
+        }
+        log { "\n" }
+
+        var maxNotifications: Int
+        if (maxNotifWithoutSavingSpace >= minCountAtHeightWithoutConstraints) {
+            saveSpaceOnLockscreen = false
+            maxNotifications = maxNotifWithoutSavingSpace
+            log {
+                "\tDo NOT save space. maxNotifications=maxNotifWithoutSavingSpace=$maxNotifications"
+            }
+        } else {
+            log { "\tSAVE space ---" }
+            saveSpaceOnLockscreen = true
+            maxNotifications =
+                stackHeightSequence.lastIndexWhile { heightResult ->
+                    canStackFitInSpace(
+                        heightResult,
+                        notifSpace = notifSpace,
+                        shelfSpace = shelfSpace
+                    ) != FitResult.NO_FIT
+                }
+            log { "\t--- maxNotifications=$maxNotifications" }
+        }
+
+        // Must update views immediately to avoid mismatches between initial HUN layout height
+        // and the height adapted to lockscreen space constraints, which causes jump cuts.
+        stack.showableChildren().toList().forEach { currentNotification ->
+            run {
+                if (currentNotification is ExpandableNotificationRow) {
+                    currentNotification.saveSpaceOnLockscreen = saveSpaceOnLockscreen
+                }
+            }
+        }
+
         if (onLockscreen()) {
             maxNotifications = min(maxKeyguardNotifications, maxNotifications)
         }
@@ -137,53 +228,80 @@
         maxNotifications = max(0, maxNotifications)
         log {
             val sequence = if (SPEW) " stackHeightSequence=${stackHeightSequence.toList()}" else ""
-            "computeMaxKeyguardNotifications(" +
-                " spaceForNotifications=$spaceForNotifications" +
-                " spaceForShelf=$spaceForShelf" +
-                " shelfHeight=$shelfIntrinsicHeight) -> $maxNotifications$sequence"
+            "--- computeMaxKeyguardNotifications(" +
+                " notifSpace=$notifSpace" +
+                " shelfSpace=$shelfSpace" +
+                " shelfHeight=$shelfHeight) -> $maxNotifications$sequence"
         }
+        log { "\n" }
         return maxNotifications
     }
 
     /**
-     * Given the [maxNotifications] constraint, calculates the height of the
+     * Given the [maxNotifs] constraint, calculates the height of the
      * [NotificationStackScrollLayout]. This might or might not be in keyguard.
      *
      * @param stack stack containing notifications as children.
-     * @param maxNotifications Maximum number of notifications. When reached, the others will go
-     * into the shelf.
-     * @param shelfIntrinsicHeight height of the shelf, without any padding. It might be zero.
-     *
+     * @param maxNotifs Maximum number of notifications. When reached, the others will go into the
+     *   shelf.
+     * @param shelfHeight height of the shelf, without any padding. It might be zero.
      * @return height of the stack, including shelf height, if needed.
      */
     fun computeHeight(
         stack: NotificationStackScrollLayout,
-        maxNotifications: Int,
-        shelfIntrinsicHeight: Float
+        maxNotifs: Int,
+        shelfHeight: Float
     ): Float {
         log { "\n" }
-        lastComputeHeightLog = ""
-        val heightPerMaxNotifications =
-            computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight,
-                    /* computeHeight= */ true)
+        log { "computeHeight ---" }
 
-        val (notificationsHeight, shelfHeightWithSpaceBefore) =
-            heightPerMaxNotifications.elementAtOrElse(maxNotifications) {
-                heightPerMaxNotifications.last() // Height with all notifications visible.
+        val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfHeight)
+
+        val (notifsHeight, notifsHeightSavingSpace, shelfHeightWithSpaceBefore) =
+            stackHeightSequence.elementAtOrElse(maxNotifs) {
+                stackHeightSequence.last() // Height with all notifications visible.
             }
-        lastComputeHeightLog += "\ncomputeHeight(maxNotifications=$maxNotifications," +
-                "shelfIntrinsicHeight=$shelfIntrinsicHeight) -> " +
-                "${notificationsHeight + shelfHeightWithSpaceBefore}" +
-                " = ($notificationsHeight + $shelfHeightWithSpaceBefore)"
-        log {
-            lastComputeHeightLog
+
+        var height: Float
+        if (saveSpaceOnLockscreen) {
+            height = notifsHeightSavingSpace + shelfHeightWithSpaceBefore
+            log {
+                "--- computeHeight(maxNotifs=$maxNotifs, shelfHeight=$shelfHeight)" +
+                    " -> $height=($notifsHeightSavingSpace+$shelfHeightWithSpaceBefore)," +
+                    " | saveSpaceOnLockscreen=$saveSpaceOnLockscreen"
+            }
+        } else {
+            height = notifsHeight + shelfHeightWithSpaceBefore
+            log {
+                "--- computeHeight(maxNotifs=$maxNotifs, shelfHeight=$shelfHeight)" +
+                    " -> ${height}=($notifsHeight+$shelfHeightWithSpaceBefore)" +
+                    " | saveSpaceOnLockscreen=$saveSpaceOnLockscreen"
+            }
         }
-        return notificationsHeight + shelfHeightWithSpaceBefore
+        return height
     }
 
+    private enum class FitResult {
+        FIT,
+        FIT_IF_SAVE_SPACE,
+        NO_FIT
+    }
+
+    data class SpaceNeeded(
+        // Float height of spaceNeeded when showing heads up layout for FSI HUNs.
+        val whenEnoughSpace: Float,
+
+        // Float height of space needed when showing collapsed layout for FSI HUNs.
+        val whenSavingSpace: Float
+    )
+
     private data class StackHeight(
         // Float height with ith max notifications (not including shelf)
-        val notificationsHeight: Float,
+        val notifsHeight: Float,
+
+        // Float height with ith max notifications
+        // (not including shelf, using collapsed layout for FSI HUN)
+        val notifsHeightSavingSpace: Float,
 
         // Float height of shelf (0 if shelf is not showing), and space before the shelf that
         // changes during the lockscreen <=> full shade transition.
@@ -193,20 +311,27 @@
     private fun computeHeightPerNotificationLimit(
         stack: NotificationStackScrollLayout,
         shelfHeight: Float,
-        computeHeight: Boolean
     ): Sequence<StackHeight> = sequence {
-        log { "computeHeightPerNotificationLimit" }
-
         val children = stack.showableChildren().toList()
         var notifications = 0f
+        var notifsWithCollapsedHun = 0f
         var previous: ExpandableView? = null
         val onLockscreen = onLockscreen()
 
         // Only shelf. This should never happen, since we allow 1 view minimum (EmptyViewState).
-        yield(StackHeight(notificationsHeight = 0f, shelfHeightWithSpaceBefore = shelfHeight))
+        yield(
+            StackHeight(
+                notifsHeight = 0f,
+                notifsHeightSavingSpace = 0f,
+                shelfHeightWithSpaceBefore = shelfHeight
+            )
+        )
 
         children.forEachIndexed { i, currentNotification ->
-            notifications += spaceNeeded(currentNotification, i, previous, stack, onLockscreen)
+            val space = getSpaceNeeded(currentNotification, i, previous, stack, onLockscreen)
+            notifications += space.whenEnoughSpace
+            notifsWithCollapsedHun += space.whenSavingSpace
+
             previous = currentNotification
 
             val shelfWithSpaceBefore =
@@ -219,22 +344,23 @@
                             stack,
                             previous = currentNotification,
                             current = children[firstViewInShelfIndex],
-                            currentIndex = firstViewInShelfIndex)
+                            currentIndex = firstViewInShelfIndex
+                        )
                     spaceBeforeShelf + shelfHeight
                 }
 
-            val currentLog = "computeHeight | i=$i notificationsHeight=$notifications " +
-                "shelfHeightWithSpaceBefore=$shelfWithSpaceBefore"
-            if (computeHeight) {
-                lastComputeHeightLog += "\n" + currentLog
-            }
             log {
-                currentLog
+                "\tcomputeHeightPerNotificationLimit i=$i notifs=$notifications " +
+                    "notifsHeightSavingSpace=$notifsWithCollapsedHun" +
+                    " shelfWithSpaceBefore=$shelfWithSpaceBefore"
             }
             yield(
                 StackHeight(
-                    notificationsHeight = notifications,
-                    shelfHeightWithSpaceBefore = shelfWithSpaceBefore))
+                    notifsHeight = notifications,
+                    notifsHeightSavingSpace = notifsWithCollapsedHun,
+                    shelfHeightWithSpaceBefore = shelfWithSpaceBefore
+                )
+            )
         }
     }
 
@@ -256,32 +382,46 @@
     }
 
     @VisibleForTesting
-    fun spaceNeeded(
+    fun getSpaceNeeded(
         view: ExpandableView,
         visibleIndex: Int,
         previousView: ExpandableView?,
         stack: NotificationStackScrollLayout,
-        onLockscreen: Boolean
-    ): Float {
+        onLockscreen: Boolean,
+    ): SpaceNeeded {
         assert(view.isShowable(onLockscreen))
 
+        // Must use heightWithoutLockscreenConstraints because intrinsicHeight references
+        // mSaveSpaceOnLockscreen and using intrinsicHeight here will result in stack overflow.
+        val height = view.heightWithoutLockscreenConstraints.toFloat()
+        val gapAndDividerHeight =
+            calculateGapAndDividerHeight(stack, previousView, current = view, visibleIndex)
+
         var size =
             if (onLockscreen) {
                 if (view is ExpandableNotificationRow && view.entry.isStickyAndNotDemoted) {
-                    view.intrinsicHeight.toFloat()
+                    height
                 } else {
                     view.getMinHeight(/* ignoreTemporaryStates= */ true).toFloat()
                 }
             } else {
-                view.intrinsicHeight.toFloat()
+                height
             }
+        size += gapAndDividerHeight
 
-        size += calculateGapAndDividerHeight(stack, previousView, current = view, visibleIndex)
-        return size
+        var sizeWhenSavingSpace =
+            if (onLockscreen) {
+                view.getMinHeight(/* ignoreTemporaryStates= */ true).toFloat()
+            } else {
+                height
+            }
+        sizeWhenSavingSpace += gapAndDividerHeight
+
+        return SpaceNeeded(size, sizeWhenSavingSpace)
     }
 
     fun dump(pw: PrintWriter, args: Array<out String>) {
-        pw.println("NotificationStackSizeCalculator lastComputeHeightLog = $lastComputeHeightLog")
+        pw.println("NotificationStackSizeCalculator saveSpaceOnLockscreen=$saveSpaceOnLockscreen")
     }
 
     private fun ExpandableView.isShowable(onLockscreen: Boolean): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index 8ee2c6f..74ab47f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.Dumpable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.util.Assert
 import com.android.systemui.util.sensors.AsyncSensorManager
@@ -46,6 +47,7 @@
     private val statusBarStateController: StatusBarStateController,
     private val asyncSensorManager: AsyncSensorManager,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    private val keyguardFaceAuthInteractor: KeyguardFaceAuthInteractor,
     private val dumpManager: DumpManager
 ) : Dumpable, CoreStartable {
 
@@ -72,6 +74,7 @@
             // Not listening anymore since trigger events unregister themselves
             isListening = false
             updateListeningState()
+            keyguardFaceAuthInteractor.onDeviceLifted()
             keyguardUpdateMonitor.requestFaceAuth(
                 FaceAuthApiRequestReason.PICK_UP_GESTURE_TRIGGERED
             )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java
deleted file mode 100644
index 076e5f1..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2014 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.phone;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.WindowInsets;
-import android.widget.FrameLayout;
-
-/**
- * A view group which contains the preview of phone/camera and draws a black bar at the bottom as
- * the fake navigation bar.
- */
-public class KeyguardPreviewContainer extends FrameLayout {
-
-    private Drawable mBlackBarDrawable = new Drawable() {
-        @Override
-        public void draw(Canvas canvas) {
-            canvas.save();
-            canvas.clipRect(0, getHeight() - getPaddingBottom(), getWidth(), getHeight());
-            canvas.drawColor(Color.BLACK);
-            canvas.restore();
-        }
-
-        @Override
-        public void setAlpha(int alpha) {
-            // noop
-        }
-
-        @Override
-        public void setColorFilter(ColorFilter colorFilter) {
-            // noop
-        }
-
-        @Override
-        public int getOpacity() {
-            return android.graphics.PixelFormat.OPAQUE;
-        }
-    };
-
-    public KeyguardPreviewContainer(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        setBackground(mBlackBarDrawable);
-    }
-
-    @Override
-    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-        setPadding(0, 0, 0, insets.getStableInsetBottom());
-        return super.onApplyWindowInsets(insets);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 118bfc5..0cd3401 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -175,15 +175,19 @@
         // We animate the Y properly separately using the PropertyAnimator, as the panel
         // view also needs to update the end position.
         PropertyAnimator.cancelAnimation(keyguardView, AnimatableProperty.Y)
-        PropertyAnimator.setProperty<View>(keyguardView, AnimatableProperty.Y, currentY,
+        PropertyAnimator.setProperty(keyguardView, AnimatableProperty.Y, currentY,
                 AnimationProperties().setDuration(duration.toLong()),
                 true /* animate */)
 
-        keyguardView.animate()
+        // Cancel any existing CUJs before starting the animation
+        interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
+
+        PropertyAnimator.setProperty(
+            keyguardView, AnimatableProperty.ALPHA, 1f,
+            AnimationProperties()
+                .setDelay(0)
                 .setDuration(duration.toLong())
-                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                .alpha(1f)
-                .withEndAction {
+                .setAnimationEndAction {
                     aodUiAnimationPlaying = false
 
                     // Lock the keyguard if it was waiting for the screen off animation to end.
@@ -199,30 +203,23 @@
                     // Done going to sleep, reset this flag.
                     decidedToAnimateGoingToSleep = null
 
-                    // We need to unset the listener. These are persistent for future animators
-                    keyguardView.animate().setListener(null)
                     interactionJankMonitor.end(CUJ_SCREEN_OFF_SHOW_AOD)
                 }
-                .setListener(object : AnimatorListenerAdapter() {
-                    override fun onAnimationCancel(animation: Animator?) {
-                        // If we're cancelled, reset state flags/listeners. The end action above
-                        // will not be called, which is what we want since that will finish the
-                        // screen off animation and show the lockscreen, which we don't want if we
-                        // were cancelled.
-                        aodUiAnimationPlaying = false
-                        decidedToAnimateGoingToSleep = null
-                        keyguardView.animate().setListener(null)
-
-                        interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
-                    }
-
-                    override fun onAnimationStart(animation: Animator?) {
-                        interactionJankMonitor.begin(
-                                mCentralSurfaces.notificationShadeWindowView,
-                                CUJ_SCREEN_OFF_SHOW_AOD)
-                    }
-                })
-                .start()
+                .setAnimationCancelAction {
+                    // If we're cancelled, reset state flags/listeners. The end action above
+                    // will not be called, which is what we want since that will finish the
+                    // screen off animation and show the lockscreen, which we don't want if we
+                    // were cancelled.
+                    aodUiAnimationPlaying = false
+                    decidedToAnimateGoingToSleep = null
+                    interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
+                }
+                .setCustomInterpolator(View.ALPHA, Interpolators.FAST_OUT_SLOW_IN),
+            true /* animate */)
+        interactionJankMonitor.begin(
+            mCentralSurfaces.notificationShadeWindowView,
+            CUJ_SCREEN_OFF_SHOW_AOD
+        )
     }
 
     override fun onStartedWakingUp() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index cc2a0ba..5d4adda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -52,8 +52,7 @@
 import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener;
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
-import com.android.systemui.statusbar.notification.shelf.view.NotificationShelfViewBinderModule;
-import com.android.systemui.statusbar.notification.shelf.view.NotificationShelfViewBinderWrapperControllerImpl;
+import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
 import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator;
@@ -87,8 +86,7 @@
 import dagger.Provides;
 import dagger.multibindings.IntoSet;
 
-@Module(subcomponents = StatusBarFragmentComponent.class,
-        includes = { NotificationShelfViewBinderModule.class })
+@Module(subcomponents = StatusBarFragmentComponent.class)
 public abstract class StatusBarViewModule {
 
     public static final String SHADE_HEADER = "large_screen_shade_header";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index eaa1455..b3d2461 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -35,6 +35,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxyImpl
 import com.android.systemui.statusbar.pipeline.mobile.util.SubscriptionManagerProxy
@@ -53,6 +54,9 @@
 import dagger.Provides
 import dagger.multibindings.ClassKey
 import dagger.multibindings.IntoMap
+import kotlinx.coroutines.flow.Flow
+import java.util.function.Supplier
+import javax.inject.Named
 
 @Module
 abstract class StatusBarPipelineModule {
@@ -115,6 +119,17 @@
 
         @Provides
         @SysUISingleton
+        @Named(FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON)
+        fun provideFirstMobileSubShowingNetworkTypeIconProvider(
+            mobileIconsViewModel: MobileIconsViewModel,
+        ): Supplier<Flow<Boolean>> {
+            return Supplier<Flow<Boolean>> {
+                mobileIconsViewModel.firstMobileSubShowingNetworkTypeIcon
+            }
+        }
+
+        @Provides
+        @SysUISingleton
         @WifiInputLog
         fun provideWifiInputLogBuffer(factory: LogBufferFactory): LogBuffer {
             return factory.create("WifiInputLog", 50)
@@ -168,5 +183,8 @@
         fun provideVerboseMobileViewLogBuffer(factory: LogBufferFactory): LogBuffer {
             return factory.create("VerboseMobileViewLog", 100)
         }
+
+        const val FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON =
+            "FirstMobileSubShowingNetworkTypeIcon"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
index 075e6ec..a05ab84 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
@@ -26,15 +26,7 @@
 import java.io.PrintWriter
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 
 /**
@@ -46,39 +38,16 @@
  * the list of available mobile lines of service for which we want to show icons.
  */
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class MobileUiAdapter
 @Inject
 constructor(
-    interactor: MobileIconsInteractor,
     private val iconController: StatusBarIconController,
-    private val iconsViewModelFactory: MobileIconsViewModel.Factory,
+    val mobileIconsViewModel: MobileIconsViewModel,
     private val logger: MobileViewLogger,
     @Application private val scope: CoroutineScope,
     private val statusBarPipelineFlags: StatusBarPipelineFlags,
 ) : CoreStartable {
-    private val mobileSubIds: Flow<List<Int>> =
-        interactor.filteredSubscriptions.mapLatest { subscriptions ->
-            subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId }
-        }
-
-    /**
-     * We expose the list of tracked subscriptions as a flow of a list of ints, where each int is
-     * the subscriptionId of the relevant subscriptions. These act as a key into the layouts which
-     * house the mobile infos.
-     *
-     * NOTE: this should go away as the view presenter learns more about this data pipeline
-     */
-    private val mobileSubIdsState: StateFlow<List<Int>> =
-        mobileSubIds
-            .distinctUntilChanged()
-            .onEach { logger.logUiAdapterSubIdsUpdated(it) }
-            .stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
-
-    /** In order to keep the logs tame, we will reuse the same top-level mobile icons view model */
-    val mobileIconsViewModel = iconsViewModelFactory.create(mobileSubIdsState)
-
     private var isCollecting: Boolean = false
     private var lastValue: List<Int>? = null
 
@@ -90,7 +59,7 @@
         if (statusBarPipelineFlags.useNewMobileIcons()) {
             scope.launch {
                 isCollecting = true
-                mobileSubIds.collectLatest {
+                mobileIconsViewModel.subscriptionIdsFlow.collectLatest {
                     logger.logUiAdapterSubIdsSentToIconController(it)
                     lastValue = it
                     iconController.setNewMobileIconSubIds(it)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
index 90dff23..f2f9143 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
@@ -41,15 +41,6 @@
 
     private val collectionStatuses = mutableMapOf<String, Boolean>()
 
-    fun logUiAdapterSubIdsUpdated(subs: List<Int>) {
-        buffer.log(
-            TAG,
-            LogLevel.INFO,
-            { str1 = subs.toString() },
-            { "Sub IDs in MobileUiAdapter updated internally: $str1" },
-        )
-    }
-
     fun logUiAdapterSubIdsSentToIconController(subs: List<Int>) {
         buffer.log(
             TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
index 2b90065..40b8c90 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -29,18 +29,26 @@
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 
 /**
  * View model for describing the system's current mobile cellular connections. The result is a list
  * of [MobileIconViewModel]s which describe the individual icons and can be bound to
- * [ModernStatusBarMobileView]
+ * [ModernStatusBarMobileView].
  */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
 class MobileIconsViewModel
 @Inject
 constructor(
-    val subscriptionIdsFlow: StateFlow<List<Int>>,
     val logger: MobileViewLogger,
     private val verboseLogger: VerboseMobileViewLogger,
     private val interactor: MobileIconsInteractor,
@@ -51,22 +59,43 @@
 ) {
     @VisibleForTesting val mobileIconSubIdCache = mutableMapOf<Int, MobileIconViewModel>()
 
+    val subscriptionIdsFlow: StateFlow<List<Int>> =
+        interactor.filteredSubscriptions
+            .mapLatest { subscriptions ->
+                subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
+
+    private val firstMobileSubViewModel: StateFlow<MobileIconViewModelCommon?> =
+        subscriptionIdsFlow
+            .map {
+                if (it.isEmpty()) {
+                    null
+                } else {
+                    // Mobile icons get reversed by [StatusBarIconController], so the last element
+                    // in this list will show up visually first.
+                    commonViewModelForSub(it.last())
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+    /**
+     * A flow that emits `true` if the mobile sub that's displayed first visually is showing its
+     * network type icon and `false` otherwise.
+     */
+    val firstMobileSubShowingNetworkTypeIcon: StateFlow<Boolean> =
+        firstMobileSubViewModel
+            .flatMapLatest { firstMobileSubViewModel ->
+                firstMobileSubViewModel?.networkTypeIcon?.map { it != null } ?: flowOf(false)
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
     init {
         scope.launch { subscriptionIdsFlow.collect { removeInvalidModelsFromCache(it) } }
     }
 
     fun viewModelForSub(subId: Int, location: StatusBarLocation): LocationBasedMobileViewModel {
-        val common =
-            mobileIconSubIdCache[subId]
-                ?: MobileIconViewModel(
-                        subId,
-                        interactor.createMobileConnectionInteractorForSubId(subId),
-                        airplaneModeInteractor,
-                        constants,
-                        scope,
-                    )
-                    .also { mobileIconSubIdCache[subId] = it }
-
+        val common = commonViewModelForSub(subId)
         return LocationBasedMobileViewModel.viewModelForLocation(
             common,
             statusBarPipelineFlags,
@@ -75,34 +104,20 @@
         )
     }
 
+    private fun commonViewModelForSub(subId: Int): MobileIconViewModelCommon {
+        return mobileIconSubIdCache[subId]
+            ?: MobileIconViewModel(
+                    subId,
+                    interactor.createMobileConnectionInteractorForSubId(subId),
+                    airplaneModeInteractor,
+                    constants,
+                    scope,
+                )
+                .also { mobileIconSubIdCache[subId] = it }
+    }
+
     private fun removeInvalidModelsFromCache(subIds: List<Int>) {
         val subIdsToRemove = mobileIconSubIdCache.keys.filter { !subIds.contains(it) }
         subIdsToRemove.forEach { mobileIconSubIdCache.remove(it) }
     }
-
-    @SysUISingleton
-    class Factory
-    @Inject
-    constructor(
-        private val logger: MobileViewLogger,
-        private val verboseLogger: VerboseMobileViewLogger,
-        private val interactor: MobileIconsInteractor,
-        private val airplaneModeInteractor: AirplaneModeInteractor,
-        private val constants: ConnectivityConstants,
-        @Application private val scope: CoroutineScope,
-        private val statusBarPipelineFlags: StatusBarPipelineFlags,
-    ) {
-        fun create(subscriptionIdsFlow: StateFlow<List<Int>>): MobileIconsViewModel {
-            return MobileIconsViewModel(
-                subscriptionIdsFlow,
-                logger,
-                verboseLogger,
-                interactor,
-                airplaneModeInteractor,
-                constants,
-                scope,
-                statusBarPipelineFlags,
-            )
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index 9e8c814..e819c4f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -36,7 +36,6 @@
 import kotlinx.coroutines.InternalCoroutinesApi
 import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.launch
 
@@ -64,6 +63,7 @@
         val activityOutView = view.requireViewById<ImageView>(R.id.wifi_out)
         val activityContainerView = view.requireViewById<View>(R.id.inout_container)
         val airplaneSpacer = view.requireViewById<View>(R.id.wifi_airplane_spacer)
+        val signalSpacer = view.requireViewById<View>(R.id.wifi_signal_spacer)
 
         view.isVisible = true
         iconView.isVisible = true
@@ -133,6 +133,12 @@
                     }
                 }
 
+                launch {
+                    viewModel.isSignalSpacerVisible.distinctUntilChanged().collect { visible ->
+                        signalSpacer.isVisible = visible
+                    }
+                }
+
                 try {
                     awaitCancellation()
                 } finally {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
index c9a0786..d9c2144 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -30,8 +30,8 @@
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
-import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
+import com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule.Companion.FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON
 import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
@@ -39,7 +39,9 @@
 import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
+import java.util.function.Supplier
 import javax.inject.Inject
+import javax.inject.Named
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
@@ -53,24 +55,24 @@
 /**
  * Models the UI state for the status bar wifi icon.
  *
- * This class exposes three view models, one per status bar location: [home], [keyguard], and [qs].
- * In order to get the UI state for the wifi icon, you must use one of those view models (whichever
- * is correct for your location).
- *
- * Internally, this class maintains the current state of the wifi icon and notifies those three view
- * models of any changes.
+ * This is a singleton so that we don't have duplicate logs and should *not* be used directly to
+ * control views. Instead, use an instance of [LocationBasedWifiViewModel]. See
+ * [LocationBasedWifiViewModel.viewModelForLocation].
  */
 @SysUISingleton
 class WifiViewModel
 @Inject
 constructor(
     airplaneModeViewModel: AirplaneModeViewModel,
+    // TODO(b/238425913): The wifi icon shouldn't need to consume mobile information. A
+    //  container-level view model should do the work instead.
+    @Named(FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON)
+    shouldShowSignalSpacerProvider: Supplier<Flow<Boolean>>,
     connectivityConstants: ConnectivityConstants,
     private val context: Context,
     @WifiTableLog wifiTableLogBuffer: TableLogBuffer,
     interactor: WifiInteractor,
     @Application private val scope: CoroutineScope,
-    statusBarPipelineFlags: StatusBarPipelineFlags,
     wifiConstants: WifiConstants,
 ) : WifiViewModelCommon {
     /** Returns the icon to use based on the given network. */
@@ -183,6 +185,8 @@
     override val isAirplaneSpacerVisible: Flow<Boolean> =
         airplaneModeViewModel.isAirplaneModeIconVisible
 
+    override val isSignalSpacerVisible: Flow<Boolean> = shouldShowSignalSpacerProvider.get()
+
     companion object {
         @StringRes
         @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelCommon.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelCommon.kt
index eccf023..617e192 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelCommon.kt
@@ -39,4 +39,7 @@
 
     /** True if the airplane spacer view should be visible. */
     val isAirplaneSpacerVisible: Flow<Boolean>
+
+    /** True if the spacer between the wifi icon and the RAT icon should be visible. */
+    val isSignalSpacerVisible: Flow<Boolean>
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 654ba04..1e63b2a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -21,6 +21,8 @@
 import static android.os.BatteryManager.EXTRA_HEALTH;
 import static android.os.BatteryManager.EXTRA_PRESENT;
 
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS;
+
 import android.annotation.WorkerThread;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -169,7 +171,8 @@
     @Override
     public void setPowerSaveMode(boolean powerSave, View view) {
         if (powerSave) mPowerSaverStartView.set(new WeakReference<>(view));
-        BatterySaverUtils.setPowerSaveMode(mContext, powerSave, /*needFirstTimeWarning*/ true);
+        BatterySaverUtils.setPowerSaveMode(mContext, powerSave, /*needFirstTimeWarning*/ true,
+                SAVER_ENABLED_QS);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
index e9f0dcb..928e011 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -61,6 +61,7 @@
  * Manages the user switcher on the Keyguard.
  */
 @KeyguardUserSwitcherScope
+@Deprecated
 public class KeyguardUserSwitcherController extends ViewController<KeyguardUserSwitcherView> {
 
     private static final String TAG = "KeyguardUserSwitcherController";
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index a20a5b2..e819f94 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -31,6 +31,7 @@
 import android.view.accessibility.AccessibilityManager
 import android.widget.ImageView
 import android.widget.TextView
+import androidx.annotation.DimenRes
 import androidx.annotation.IdRes
 import androidx.annotation.VisibleForTesting
 import com.android.internal.widget.CachingIconView
@@ -180,8 +181,9 @@
 
         // Button
         val buttonView = currentView.requireViewById<TextView>(R.id.end_button)
-        if (newInfo.endItem is ChipbarEndItem.Button) {
-            TextViewBinder.bind(buttonView, newInfo.endItem.text)
+        val hasButton = newInfo.endItem is ChipbarEndItem.Button
+        if (hasButton) {
+            TextViewBinder.bind(buttonView, (newInfo.endItem as ChipbarEndItem.Button).text)
 
             val onClickListener =
                 View.OnClickListener { clickedView ->
@@ -196,6 +198,12 @@
             buttonView.visibility = View.GONE
         }
 
+        currentView
+            .getInnerView()
+            .setEndPadding(
+                if (hasButton) R.dimen.chipbar_outer_padding_half else R.dimen.chipbar_outer_padding
+            )
+
         // ---- Overall accessibility ----
         val iconDesc = newInfo.startIcon.icon.contentDescription
         val loadedIconDesc =
@@ -309,6 +317,15 @@
         viewUtil.setRectToViewWindowLocation(view, outRect)
     }
 
+    private fun View.setEndPadding(@DimenRes endPaddingDimen: Int) {
+        this.setPaddingRelative(
+            this.paddingStart,
+            this.paddingTop,
+            context.resources.getDimensionPixelSize(endPaddingDimen),
+            this.paddingBottom,
+        )
+    }
+
     private fun Boolean.visibleIfTrue(): Int {
         return if (this) {
             View.VISIBLE
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
index 6e58f22..52f2d11 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
@@ -18,7 +18,7 @@
 
 import android.os.VibrationEffect
 import android.view.View
-import androidx.annotation.ColorRes
+import androidx.annotation.AttrRes
 import com.android.systemui.R
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.common.shared.model.TintedIcon
@@ -49,7 +49,9 @@
     override val priority: ViewPriority,
 ) : TemporaryViewInfo() {
     companion object {
-        @ColorRes val DEFAULT_ICON_TINT = R.color.chipbar_text_and_icon_color
+        // LINT.IfChange
+        @AttrRes val DEFAULT_ICON_TINT = com.android.internal.R.attr.materialColorOnSecondaryFixed
+        // LINT.ThenChange(systemui/res/layout/chipbar.xml)
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
index 05e5666..29f16c7 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
@@ -272,10 +272,10 @@
 
     private static boolean showApplicationIcon(ApplicationInfo appInfo,
             PackageManager packageManager) {
-        if (hasFlag(appInfo.flags, FLAG_UPDATED_SYSTEM_APP)) {
+        if (hasFlag(appInfo.flags, FLAG_UPDATED_SYSTEM_APP | FLAG_SYSTEM)) {
             return packageManager.getLaunchIntentForPackage(appInfo.packageName) != null;
         }
-        return !hasFlag(appInfo.flags, FLAG_SYSTEM);
+        return true;
     }
 
     private static boolean hasFlag(int flags, int flag) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt
new file mode 100644
index 0000000..c587f2e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.util.kotlin
+
+import dagger.Lazy
+import kotlin.reflect.KProperty
+
+/**
+ * Extension operator that allows developers to use [dagger.Lazy] as a property delegate:
+ * ```kotlin
+ *    class MyClass @Inject constructor(
+ *      lazyDependency: dagger.Lazy<Foo>,
+ *    ) {
+ *      val dependency: Foo by lazyDependency
+ *    }
+ * ```
+ */
+operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = get()
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
index 209ea41..58cffa7 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
@@ -58,6 +58,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -418,6 +419,7 @@
         @Inject
         public MemoryTile(
                 QSHost host,
+                QsEventLogger uiEventLogger,
                 @Background Looper backgroundLooper,
                 @Main Handler mainHandler,
                 FalsingManager falsingManager,
@@ -428,7 +430,7 @@
                 GarbageMonitor monitor,
                 PanelInteractor panelInteractor
         ) {
-            super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+            super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                     statusBarStateController, activityStarter, qsLogger);
             gm = monitor;
             mPanelInteractor = panelInteractor;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
index 35af444..e3ed2b4 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
@@ -16,20 +16,34 @@
 
 package com.android.systemui.volume;
 
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
+
 import android.annotation.StringRes;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.media.AudioManager;
-import android.os.CountDownTimer;
+import android.provider.Settings;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.WindowManager;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.messages.nano.SystemMessageProto;
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.util.NotificationChannels;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
 
 /**
  * A class that implements the four Computed Sound Dose-related warnings defined in {@link AudioManager}:
@@ -53,34 +67,58 @@
  * communication between the native audio framework that implements the dose computation and the
  * audio service.
  */
-public abstract class CsdWarningDialog extends SystemUIDialog
+public class CsdWarningDialog extends SystemUIDialog
         implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener {
 
     private static final String TAG = Util.logTag(CsdWarningDialog.class);
 
     private static final int KEY_CONFIRM_ALLOWED_AFTER_MS = 1000; // milliseconds
     // time after which action is taken when the user hasn't ack'd or dismissed the dialog
-    private static final int NO_ACTION_TIMEOUT_MS = 5000;
+    public static final int NO_ACTION_TIMEOUT_MS = 5000;
 
     private final Context mContext;
     private final AudioManager mAudioManager;
     private final @AudioManager.CsdWarning int mCsdWarning;
     private final Object mTimerLock = new Object();
+
     /**
      * Timer to keep track of how long the user has before an action (here volume reduction) is
      * taken on their behalf.
      */
     @GuardedBy("mTimerLock")
-    private final CountDownTimer mNoUserActionTimer;
+    private Runnable mNoUserActionRunnable;
+    private Runnable mCancelScheduledNoUserActionRunnable = null;
+
+    private final DelayableExecutor mDelayableExecutor;
+    private NotificationManager mNotificationManager;
+    private Runnable mOnCleanup;
 
     private long mShowTime;
 
-    public CsdWarningDialog(@AudioManager.CsdWarning int csdWarning, Context context,
-            AudioManager audioManager) {
+    /**
+     * To inject dependencies and allow for easier testing
+     */
+    @AssistedFactory
+    public interface Factory {
+        /**
+         * Create a dialog object
+         */
+        CsdWarningDialog create(int csdWarning, Runnable onCleanup);
+    }
+
+    @AssistedInject
+    public CsdWarningDialog(@Assisted @AudioManager.CsdWarning int csdWarning, Context context,
+            AudioManager audioManager, NotificationManager notificationManager,
+            @Background DelayableExecutor delayableExecutor, @Assisted Runnable onCleanup) {
         super(context);
         mCsdWarning = csdWarning;
         mContext = context;
         mAudioManager = audioManager;
+        mNotificationManager = notificationManager;
+        mOnCleanup = onCleanup;
+
+        mDelayableExecutor = delayableExecutor;
+
         getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
         setShowForAllUsers(true);
         setMessage(mContext.getString(getStringForWarning(csdWarning)));
@@ -95,25 +133,24 @@
                 Context.RECEIVER_EXPORTED_UNAUDITED);
 
         if (csdWarning == AudioManager.CSD_WARNING_DOSE_REACHED_1X) {
-            mNoUserActionTimer = new CountDownTimer(NO_ACTION_TIMEOUT_MS, NO_ACTION_TIMEOUT_MS) {
-                @Override
-                public void onTick(long millisUntilFinished) { }
-
-                @Override
-                public void onFinish() {
-                    if (mCsdWarning == AudioManager.CSD_WARNING_DOSE_REACHED_1X) {
-                        // unlike on the 5x dose repeat, level is only reduced to RS1
-                        // when the warning is not acknowledged quick enough
-                        mAudioManager.lowerVolumeToRs1();
-                    }
+            mNoUserActionRunnable = () -> {
+                if (mCsdWarning == AudioManager.CSD_WARNING_DOSE_REACHED_1X) {
+                    // unlike on the 5x dose repeat, level is only reduced to RS1 when the warning
+                    // is not acknowledged quickly enough
+                    mAudioManager.lowerVolumeToRs1();
+                    sendNotification();
                 }
             };
         } else {
-            mNoUserActionTimer = null;
+            mNoUserActionRunnable = null;
         }
     }
 
-    protected abstract void cleanUp();
+    private void cleanUp() {
+        if (mOnCleanup != null) {
+            mOnCleanup.run();
+        }
+    }
 
     // NOT overriding onKeyDown as we're not allowing a dismissal on any key other than
     // VOLUME_DOWN, and for this, we don't need to track if it's the start of a new
@@ -153,12 +190,9 @@
         super.onStart();
         mShowTime = System.currentTimeMillis();
         synchronized (mTimerLock) {
-            if (mNoUserActionTimer != null) {
-                new Thread(() -> {
-                    synchronized (mTimerLock) {
-                        mNoUserActionTimer.start();
-                    }
-                }).start();
+            if (mNoUserActionRunnable != null) {
+                mCancelScheduledNoUserActionRunnable = mDelayableExecutor.executeDelayed(
+                        mNoUserActionRunnable, NO_ACTION_TIMEOUT_MS);
             }
         }
     }
@@ -166,8 +200,8 @@
     @Override
     protected void onStop() {
         synchronized (mTimerLock) {
-            if (mNoUserActionTimer != null) {
-                mNoUserActionTimer.cancel();
+            if (mCancelScheduledNoUserActionRunnable != null) {
+                mCancelScheduledNoUserActionRunnable.run();
             }
         }
     }
@@ -212,4 +246,32 @@
         Log.e(TAG, "Invalid CSD warning event " + csdWarning, new Exception());
         return com.android.internal.R.string.csd_dose_reached_warning;
     }
+
+
+    /**
+     * In case user did not respond to the dialog, they still need to know volume was lowered.
+     */
+    private void sendNotification() {
+        Intent intent = new Intent(Settings.ACTION_SOUND_SETTINGS);
+        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent,
+                FLAG_IMMUTABLE);
+
+        String text = mContext.getString(R.string.csd_system_lowered_text);
+        String title = mContext.getString(R.string.csd_lowered_title);
+
+        Notification.Builder builder =
+                new Notification.Builder(mContext, NotificationChannels.ALERTS)
+                        .setSmallIcon(R.drawable.hearing)
+                        .setContentTitle(title)
+                        .setContentText(text)
+                        .setContentIntent(pendingIntent)
+                        .setStyle(new Notification.BigTextStyle().bigText(text))
+                        .setVisibility(Notification.VISIBILITY_PUBLIC)
+                        .setLocalOnly(true)
+                        .setAutoCancel(true)
+                        .setCategory(Notification.CATEGORY_SYSTEM);
+
+        mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_CSD_LOWER_AUDIO,
+                builder.build());
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 95cc12a..3c007f9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -263,6 +263,7 @@
     private final ConfigurationController mConfigurationController;
     private final MediaOutputDialogFactory mMediaOutputDialogFactory;
     private final VolumePanelFactory mVolumePanelFactory;
+    private final CsdWarningDialog.Factory mCsdWarningDialogFactory;
     private final ActivityStarter mActivityStarter;
 
     private boolean mShowing;
@@ -311,6 +312,7 @@
             InteractionJankMonitor interactionJankMonitor,
             DeviceConfigProxy deviceConfigProxy,
             Executor executor,
+            CsdWarningDialog.Factory csdWarningDialogFactory,
             DumpManager dumpManager) {
         mContext =
                 new ContextThemeWrapper(context, R.style.volume_dialog_theme);
@@ -322,6 +324,7 @@
         mConfigurationController = configurationController;
         mMediaOutputDialogFactory = mediaOutputDialogFactory;
         mVolumePanelFactory = volumePanelFactory;
+        mCsdWarningDialogFactory = csdWarningDialogFactory;
         mActivityStarter = activityStarter;
         mShowActiveStreamOnly = showActiveStreamOnly();
         mHasSeenODICaptionsTooltip =
@@ -2084,21 +2087,21 @@
         rescheduleTimeoutH();
     }
 
-    private void showCsdWarningH(int csdWarning, int durationMs) {
+    @VisibleForTesting void showCsdWarningH(int csdWarning, int durationMs) {
         synchronized (mSafetyWarningLock) {
+
             if (mCsdDialog != null) {
                 return;
             }
-            mCsdDialog = new CsdWarningDialog(csdWarning,
-                    mContext, mController.getAudioManager()) {
-                @Override
-                protected void cleanUp() {
-                    synchronized (mSafetyWarningLock) {
-                        mCsdDialog = null;
-                    }
-                    recheckH(null);
+
+            final Runnable cleanUp = () -> {
+                synchronized (mSafetyWarningLock) {
+                    mCsdDialog = null;
                 }
+                recheckH(null);
             };
+
+            mCsdDialog = mCsdWarningDialogFactory.create(csdWarning, cleanUp);
             mCsdDialog.show();
         }
         recheckH(null);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index 0ab6c69..14d3ca3 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -30,17 +30,18 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.util.DeviceConfigProxy;
+import com.android.systemui.volume.CsdWarningDialog;
 import com.android.systemui.volume.VolumeComponent;
 import com.android.systemui.volume.VolumeDialogComponent;
 import com.android.systemui.volume.VolumeDialogImpl;
 import com.android.systemui.volume.VolumePanelFactory;
 
-import java.util.concurrent.Executor;
-
 import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
 
+import java.util.concurrent.Executor;
+
 
 /** Dagger Module for code in the volume package. */
 @Module
@@ -63,6 +64,7 @@
             InteractionJankMonitor interactionJankMonitor,
             DeviceConfigProxy deviceConfigProxy,
             @Main Executor executor,
+            CsdWarningDialog.Factory csdFactory,
             DumpManager dumpManager) {
         VolumeDialogImpl impl = new VolumeDialogImpl(
                 context,
@@ -76,6 +78,7 @@
                 interactionJankMonitor,
                 deviceConfigProxy,
                 executor,
+                csdFactory,
                 dumpManager);
         impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
         impl.setAutomute(true);
diff --git a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java
deleted file mode 100644
index e93e862..0000000
--- a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java
+++ /dev/null
@@ -1,174 +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 androidx.core.animation;
-
-import android.os.Looper;
-import android.os.SystemClock;
-import android.util.AndroidRuntimeException;
-
-import androidx.annotation.NonNull;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * NOTE: this is a copy of the {@link androidx.core.animation.AnimatorTestRule} which attempts to
- * circumvent the problems with {@link androidx.core.animation.AnimationHandler} having a static
- * list of callbacks.
- *
- * TODO(b/275602127): remove this and use the original rule once we have the updated androidx code.
- */
-public final class AnimatorTestRule2 implements TestRule {
-
-    class TestAnimationHandler extends AnimationHandler {
-        TestAnimationHandler() {
-            super(new TestProvider());
-        }
-
-        List<AnimationFrameCallback> animationCallbacks = new ArrayList<>();
-
-        @Override
-        void addAnimationFrameCallback(AnimationFrameCallback callback) {
-            animationCallbacks.add(callback);
-            callback.doAnimationFrame(getCurrentTime());
-        }
-
-        @Override
-        public void removeCallback(AnimationFrameCallback callback) {
-            int id = animationCallbacks.indexOf(callback);
-            if (id >= 0) {
-                animationCallbacks.set(id, null);
-            }
-        }
-
-        void onAnimationFrame(long frameTime) {
-            for (int i = 0; i < animationCallbacks.size(); i++) {
-                final AnimationFrameCallback callback = animationCallbacks.get(i);
-                if (callback == null) {
-                    continue;
-                }
-                callback.doAnimationFrame(frameTime);
-            }
-        }
-
-        @Override
-        void autoCancelBasedOn(ObjectAnimator objectAnimator) {
-            for (int i = animationCallbacks.size() - 1; i >= 0; i--) {
-                AnimationFrameCallback cb = animationCallbacks.get(i);
-                if (cb == null) {
-                    continue;
-                }
-                if (objectAnimator.shouldAutoCancel(cb)) {
-                    ((Animator) animationCallbacks.get(i)).cancel();
-                }
-            }
-        }
-    }
-
-    final TestAnimationHandler mTestHandler;
-    final long mStartTime;
-    private long mTotalTimeDelta = 0;
-    private final Object mLock = new Object();
-
-    public AnimatorTestRule2() {
-        mStartTime = SystemClock.uptimeMillis();
-        mTestHandler = new TestAnimationHandler();
-    }
-
-    @NonNull
-    @Override
-    public Statement apply(@NonNull final Statement base, @NonNull Description description) {
-        return new Statement() {
-            @Override
-            public void evaluate() throws Throwable {
-                AnimationHandler.setTestHandler(mTestHandler);
-                try {
-                    base.evaluate();
-                } finally {
-                    AnimationHandler.setTestHandler(null);
-                }
-            }
-        };
-    }
-
-    /**
-     * Advances the animation clock by the given amount of delta in milliseconds. This call will
-     * produce an animation frame to all the ongoing animations. This method needs to be
-     * called on the same thread as {@link Animator#start()}.
-     *
-     * @param timeDelta the amount of milliseconds to advance
-     */
-    public void advanceTimeBy(long timeDelta) {
-        if (Looper.myLooper() == null) {
-            // Throw an exception
-            throw new AndroidRuntimeException("AnimationTestRule#advanceTimeBy(long) may only be"
-                    + "called on Looper threads");
-        }
-        synchronized (mLock) {
-            // Advance time & pulse a frame
-            mTotalTimeDelta += timeDelta < 0 ? 0 : timeDelta;
-        }
-        // produce a frame
-        mTestHandler.onAnimationFrame(getCurrentTime());
-    }
-
-
-    /**
-     * Returns the current time in milliseconds tracked by AnimationHandler. Note that this is a
-     * different time than the time tracked by {@link SystemClock} This method needs to be called on
-     * the same thread as {@link Animator#start()}.
-     */
-    public long getCurrentTime() {
-        if (Looper.myLooper() == null) {
-            // Throw an exception
-            throw new AndroidRuntimeException("AnimationTestRule#getCurrentTime() may only be"
-                    + "called on Looper threads");
-        }
-        synchronized (mLock) {
-            return mStartTime + mTotalTimeDelta;
-        }
-    }
-
-
-    private class TestProvider implements AnimationHandler.AnimationFrameCallbackProvider {
-        TestProvider() {
-        }
-
-        @Override
-        public void onNewCallbackAdded(AnimationHandler.AnimationFrameCallback callback) {
-            callback.doAnimationFrame(getCurrentTime());
-        }
-
-        @Override
-        public void postFrameCallback() {
-        }
-
-        @Override
-        public void setFrameDelay(long delay) {
-        }
-
-        @Override
-        public long getFrameDelay() {
-            return 0;
-        }
-    }
-}
-
diff --git a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt
index bddd60b..e7738af 100644
--- a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt
+++ b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt
@@ -30,7 +30,7 @@
 @RunWithLooper(setAsMainLooper = true)
 class AnimatorTestRuleTest : SysuiTestCase() {
 
-    @get:Rule val animatorTestRule = AnimatorTestRule2()
+    @get:Rule val animatorTestRule = AnimatorTestRule()
 
     @Test
     fun testA() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 2f627cb..fc906de 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -48,8 +48,10 @@
 import com.android.systemui.plugins.ClockAnimations;
 import com.android.systemui.plugins.ClockController;
 import com.android.systemui.plugins.ClockEvents;
+import com.android.systemui.plugins.ClockFaceConfig;
 import com.android.systemui.plugins.ClockFaceController;
 import com.android.systemui.plugins.ClockFaceEvents;
+import com.android.systemui.plugins.ClockTickRate;
 import com.android.systemui.plugins.log.LogBuffer;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.clocks.AnimatableClockView;
@@ -185,6 +187,10 @@
         when(mClockController.getAnimations()).thenReturn(mClockAnimations);
         when(mClockRegistry.createCurrentClock()).thenReturn(mClockController);
         when(mClockEventController.getClock()).thenReturn(mClockController);
+        when(mSmallClockController.getConfig())
+                .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false));
+        when(mLargeClockController.getConfig())
+                .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false));
 
         mSliceView = new View(getContext());
         when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView);
@@ -367,6 +373,28 @@
     }
 
     @Test
+    public void testChangeClockDateWeatherEnabled_SetsDateWeatherViewVisibility() {
+        ArgumentCaptor<ClockRegistry.ClockChangeListener> listenerArgumentCaptor =
+                ArgumentCaptor.forClass(ClockRegistry.ClockChangeListener.class);
+        when(mSmartspaceController.isEnabled()).thenReturn(true);
+        when(mSmartspaceController.isDateWeatherDecoupled()).thenReturn(true);
+        when(mSmartspaceController.isWeatherEnabled()).thenReturn(true);
+        mController.init();
+        mExecutor.runAllReady();
+        assertEquals(View.VISIBLE, mFakeDateView.getVisibility());
+
+        when(mSmallClockController.getConfig())
+                .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true));
+        when(mLargeClockController.getConfig())
+                .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true));
+        verify(mClockRegistry).registerClockChangeListener(listenerArgumentCaptor.capture());
+        listenerArgumentCaptor.getValue().onCurrentClockChanged();
+
+        mExecutor.runAllReady();
+        assertEquals(View.INVISIBLE, mFakeDateView.getVisibility());
+    }
+
+    @Test
     public void testGetClock_nullClock_returnsNull() {
         when(mClockEventController.getClock()).thenReturn(null);
         assertNull(mController.getClock());
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index d760189..3e4fd89 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -67,6 +67,7 @@
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
@@ -208,7 +209,8 @@
                 mConfigurationController, mFalsingCollector, mFalsingManager,
                 mUserSwitcherController, mFeatureFlags, mGlobalSettings,
                 mSessionTracker, Optional.of(mSideFpsController), mFalsingA11yDelegate,
-                mTelephonyManager, mViewMediatorCallback, mAudioManager);
+                mTelephonyManager, mViewMediatorCallback, mAudioManager,
+                mock(KeyguardFaceAuthInteractor.class));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index f1ee108..2c1d2ad 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -16,7 +16,9 @@
 
 package com.android.keyguard;
 
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
@@ -25,6 +27,9 @@
 import com.android.keyguard.logging.KeyguardLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.plugins.ClockConfig;
+import com.android.systemui.plugins.ClockController;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -76,7 +81,16 @@
                 mScreenOffAnimationController,
                 mKeyguardLogger,
                 mFeatureFlags,
-                mInteractionJankMonitor);
+                mInteractionJankMonitor) {
+                    @Override
+                    void setProperty(
+                            AnimatableProperty property,
+                            float value,
+                            boolean animate) {
+                        // Route into the mock version for verification
+                        mControllerMock.setProperty(property, value, animate);
+                    }
+                };
     }
 
     @Test
@@ -111,4 +125,34 @@
         configurationListenerArgumentCaptor.getValue().onLocaleListChanged();
         verify(mKeyguardClockSwitchController).onLocaleListChanged();
     }
+
+    @Test
+    public void updatePosition_primaryClockAnimation() {
+        ClockController mockClock = mock(ClockController.class);
+        when(mKeyguardClockSwitchController.getClock()).thenReturn(mockClock);
+        when(mockClock.getConfig()).thenReturn(new ClockConfig(false, false, true));
+
+        mController.updatePosition(10, 15, 20f, true);
+
+        verify(mControllerMock).setProperty(AnimatableProperty.Y, 15f, true);
+        verify(mKeyguardClockSwitchController).updatePosition(
+                10, 20f, KeyguardStatusViewController.CLOCK_ANIMATION_PROPERTIES, true);
+        verify(mControllerMock).setProperty(AnimatableProperty.SCALE_X, 1f, true);
+        verify(mControllerMock).setProperty(AnimatableProperty.SCALE_Y, 1f, true);
+    }
+
+    @Test
+    public void updatePosition_alternateClockAnimation() {
+        ClockController mockClock = mock(ClockController.class);
+        when(mKeyguardClockSwitchController.getClock()).thenReturn(mockClock);
+        when(mockClock.getConfig()).thenReturn(new ClockConfig(false, true, true));
+
+        mController.updatePosition(10, 15, 20f, true);
+
+        verify(mControllerMock).setProperty(AnimatableProperty.Y, 15f, true);
+        verify(mKeyguardClockSwitchController).updatePosition(
+                10, 1f, KeyguardStatusViewController.CLOCK_ANIMATION_PROPERTIES, true);
+        verify(mControllerMock).setProperty(AnimatableProperty.SCALE_X, 20f, true);
+        verify(mControllerMock).setProperty(AnimatableProperty.SCALE_Y, 20f, true);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 0978c82..9c36af3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -18,6 +18,7 @@
 
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
 import static android.view.Choreographer.FrameCallback;
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
@@ -172,6 +173,12 @@
                 returnsSecondArg());
 
         mResources = getContext().getOrCreateTestableResources().getResources();
+        // prevent the config orientation from undefined, which may cause config.diff method
+        // neglecting the orientation update.
+        if (mResources.getConfiguration().orientation == ORIENTATION_UNDEFINED) {
+            mResources.getConfiguration().orientation = ORIENTATION_PORTRAIT;
+        }
+
         mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
                 mContext, mValueAnimator);
         mWindowMagnificationController =
@@ -688,7 +695,11 @@
 
     @Test
     public void enableWindowMagnification_rotationIsChanged_updateRotationValue() {
-        final Configuration config = mContext.getResources().getConfiguration();
+        // the config orientation should not be undefined, since it would cause config.diff
+        // returning 0 and thus the orientation changed would not be detected
+        assertNotEquals(ORIENTATION_UNDEFINED, mResources.getConfiguration().orientation);
+
+        final Configuration config = mResources.getConfiguration();
         config.orientation = config.orientation == ORIENTATION_LANDSCAPE ? ORIENTATION_PORTRAIT
                 : ORIENTATION_LANDSCAPE;
         final int newRotation = simulateRotateTheDevice();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt
index 921f9a8..2b95973 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt
@@ -37,7 +37,13 @@
 
     @Test
     fun onBackProgressed_shouldInvoke_onBackProgress() {
-        val backEvent = BackEvent(0f, 0f, 0f, BackEvent.EDGE_LEFT)
+        val backEvent =
+            BackEvent(
+                /* touchX = */ 0f,
+                /* touchY = */ 0f,
+                /* progress = */ 0f,
+                /* swipeEdge = */ BackEvent.EDGE_LEFT
+            )
         onBackAnimationCallback.onBackStarted(backEvent)
 
         onBackAnimationCallback.onBackProgressed(backEvent)
@@ -47,7 +53,13 @@
 
     @Test
     fun onBackStarted_shouldInvoke_onBackStart() {
-        val backEvent = BackEvent(0f, 0f, 0f, BackEvent.EDGE_LEFT)
+        val backEvent =
+            BackEvent(
+                /* touchX = */ 0f,
+                /* touchY = */ 0f,
+                /* progress = */ 0f,
+                /* swipeEdge = */ BackEvent.EDGE_LEFT
+            )
 
         onBackAnimationCallback.onBackStarted(backEvent)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index b4696e4..f914e75 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -170,24 +170,16 @@
     }
 
     @Test
-    fun testFocusLossAfterRotating() {
+    fun testActionCancel_panelInteractionDetectorDisable() {
         val container = initializeFingerprintContainer()
-        waitForIdleSync()
-
-        val requestID = authContainer?.requestId ?: 0L
-
-        verify(callback).onDialogAnimatedIn(requestID)
-        container.onOrientationChanged()
-        container.onWindowFocusChanged(false)
-        waitForIdleSync()
-
-        verify(callback, never()).onDismissed(
-                eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
-                eq<ByteArray?>(null), /* credentialAttestation */
-                eq(requestID)
+        container.mBiometricCallback.onAction(
+                AuthBiometricView.Callback.ACTION_USER_CANCELED
         )
+        waitForIdleSync()
+        verify(panelInteractionDetector).disable()
     }
 
+
     @Test
     fun testActionAuthenticated_sendsDismissedAuthenticated() {
         val container = initializeFingerprintContainer()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
index b41053c..ef750be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
@@ -51,32 +51,37 @@
     @Test
     fun testEnableDetector_expandWithTrack_shouldPostRunnable() {
         detector.enable(action)
-        // simulate notification expand
-        shadeExpansionStateManager.onPanelExpansionChanged(5566f, true, true, 5566f)
+        shadeExpansionStateManager.onPanelExpansionChanged(1.0f, true, true, 0f)
         verify(action).run()
     }
 
     @Test
     fun testEnableDetector_trackOnly_shouldPostRunnable() {
         detector.enable(action)
-        // simulate notification expand
-        shadeExpansionStateManager.onPanelExpansionChanged(5566f, false, true, 5566f)
+        shadeExpansionStateManager.onPanelExpansionChanged(1.0f, false, true, 0f)
         verify(action).run()
     }
 
     @Test
     fun testEnableDetector_expandOnly_shouldPostRunnable() {
         detector.enable(action)
-        // simulate notification expand
-        shadeExpansionStateManager.onPanelExpansionChanged(5566f, true, false, 5566f)
+        shadeExpansionStateManager.onPanelExpansionChanged(1.0f, true, false, 0f)
         verify(action).run()
     }
 
     @Test
+    fun testEnableDetector_expandWithoutFraction_shouldPostRunnable() {
+        detector.enable(action)
+        // simulate headsup notification
+        shadeExpansionStateManager.onPanelExpansionChanged(0.0f, true, false, 0f)
+        verifyZeroInteractions(action)
+    }
+
+    @Test
     fun testEnableDetector_shouldNotPostRunnable() {
         detector.enable(action)
         detector.disable()
-        shadeExpansionStateManager.onPanelExpansionChanged(5566f, true, true, 5566f)
+        shadeExpansionStateManager.onPanelExpansionChanged(1.0f, true, true, 0f)
         verifyZeroInteractions(action)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 64c028e..eae95a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -87,6 +87,7 @@
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.FalsingManager;
@@ -311,7 +312,8 @@
                 mUnlockedScreenOffAnimationController, mSystemUIDialogManager, mLatencyTracker,
                 mActivityLaunchAnimator, alternateTouchProvider, mBiometricExecutor,
                 mPrimaryBouncerInteractor, mSinglePointerTouchProcessor, mSessionTracker,
-                mAlternateBouncerInteractor, mSecureSettings, mInputManager, mUdfpsUtils);
+                mAlternateBouncerInteractor, mSecureSettings, mInputManager, mUdfpsUtils,
+                mock(KeyguardFaceAuthInteractor.class));
         verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
         mOverlayController = mOverlayCaptor.getValue();
         verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
index 08427da..21397d97 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
@@ -269,6 +269,30 @@
     }
 
     @Test
+    public void testInputEventPropagationAfterRemoval() {
+        final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+
+        final Environment environment = new Environment(Stream.of(touchHandler)
+                .collect(Collectors.toCollection(HashSet::new)));
+
+        final InputEvent initialEvent = Mockito.mock(InputEvent.class);
+        environment.publishInputEvent(initialEvent);
+
+        // Ensure session started
+        final DreamTouchHandler.TouchSession session = captureSession(touchHandler);
+        final InputChannelCompat.InputEventListener eventListener =
+                registerInputEventListener(session);
+
+        session.pop();
+        environment.executeAll();
+
+        final InputEvent event = Mockito.mock(InputEvent.class);
+        environment.publishInputEvent(event);
+
+        verify(eventListener, never()).onInputEvent(eq(event));
+    }
+
+    @Test
     public void testInputGesturePropagation() {
         final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 6e002f5..fc75d47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -44,22 +44,28 @@
 import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.AuthenticationStatus
 import com.android.systemui.keyguard.shared.model.DetectionStatus
 import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.keyguard.shared.model.WakeSleepReason
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.log.FaceAuthenticationLogger
 import com.android.systemui.log.SessionTracker
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.phone.FakeKeyguardStateController
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.util.mockito.KotlinArgumentCaptor
+import com.android.systemui.util.mockito.captureMany
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.util.time.SystemClock
 import com.google.common.truth.Truth.assertThat
 import java.io.PrintWriter
@@ -81,6 +87,7 @@
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Captor
 import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.isNull
 import org.mockito.Mockito.mock
@@ -115,11 +122,13 @@
     private lateinit var faceLockoutResetCallback: ArgumentCaptor<FaceManager.LockoutResetCallback>
     private lateinit var testDispatcher: TestDispatcher
 
+    private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
     private lateinit var testScope: TestScope
     private lateinit var fakeUserRepository: FakeUserRepository
     private lateinit var authStatus: FlowValue<AuthenticationStatus?>
     private lateinit var detectStatus: FlowValue<DetectionStatus?>
     private lateinit var authRunning: FlowValue<Boolean?>
+    private lateinit var bypassEnabled: FlowValue<Boolean?>
     private lateinit var lockedOut: FlowValue<Boolean?>
     private lateinit var canFaceAuthRun: FlowValue<Boolean?>
     private lateinit var authenticated: FlowValue<Boolean?>
@@ -180,8 +189,14 @@
     private fun createDeviceEntryFaceAuthRepositoryImpl(
         fmOverride: FaceManager? = faceManager,
         bypassControllerOverride: KeyguardBypassController? = bypassController
-    ) =
-        DeviceEntryFaceAuthRepositoryImpl(
+    ): DeviceEntryFaceAuthRepositoryImpl {
+        val systemClock = FakeSystemClock()
+        val faceAuthBuffer = TableLogBuffer(10, "face auth", systemClock)
+        val faceDetectBuffer = TableLogBuffer(10, "face detect", systemClock)
+        keyguardTransitionRepository = FakeKeyguardTransitionRepository()
+        val keyguardTransitionInteractor =
+            KeyguardTransitionInteractor(keyguardTransitionRepository)
+        return DeviceEntryFaceAuthRepositoryImpl(
             mContext,
             fmOverride,
             fakeUserRepository,
@@ -197,8 +212,12 @@
             keyguardRepository,
             keyguardInteractor,
             alternateBouncerInteractor,
+            faceDetectBuffer,
+            faceAuthBuffer,
+            keyguardTransitionInteractor,
             dumpManager,
         )
+    }
 
     @Test
     fun faceAuthRunsAndProvidesAuthStatusUpdates() =
@@ -726,6 +745,23 @@
         }
 
     @Test
+    fun isBypassEnabledReflectsBypassControllerState() =
+        testScope.runTest {
+            initCollectors()
+            runCurrent()
+            val listeners = captureMany {
+                verify(bypassController, atLeastOnce())
+                    .registerOnBypassStateChangedListener(capture())
+            }
+
+            listeners.forEach { it.onBypassStateChanged(true) }
+            assertThat(bypassEnabled()).isTrue()
+
+            listeners.forEach { it.onBypassStateChanged(false) }
+            assertThat(bypassEnabled()).isFalse()
+        }
+
+    @Test
     fun detectDoesNotRunWhenNonStrongBiometricIsAllowed() =
         testScope.runTest {
             testGatingCheckForDetect {
@@ -744,6 +780,50 @@
             }
         }
 
+    @Test
+    fun schedulesFaceManagerWatchdogWhenKeyguardIsGoneFromDozing() =
+        testScope.runTest {
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.DOZING, to = KeyguardState.GONE)
+            )
+
+            runCurrent()
+            verify(faceManager).scheduleWatchdog()
+        }
+
+    @Test
+    fun schedulesFaceManagerWatchdogWhenKeyguardIsGoneFromAod() =
+        testScope.runTest {
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.AOD, to = KeyguardState.GONE)
+            )
+
+            runCurrent()
+            verify(faceManager).scheduleWatchdog()
+        }
+
+    @Test
+    fun schedulesFaceManagerWatchdogWhenKeyguardIsGoneFromLockscreen() =
+        testScope.runTest {
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+            )
+
+            runCurrent()
+            verify(faceManager).scheduleWatchdog()
+        }
+
+    @Test
+    fun schedulesFaceManagerWatchdogWhenKeyguardIsGoneFromBouncer() =
+        testScope.runTest {
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.PRIMARY_BOUNCER, to = KeyguardState.GONE)
+            )
+
+            runCurrent()
+            verify(faceManager).scheduleWatchdog()
+        }
+
     private suspend fun TestScope.testGatingCheckForFaceAuth(gatingCheckModifier: () -> Unit) {
         initCollectors()
         allPreconditionsToRunFaceAuthAreTrue()
@@ -844,6 +924,7 @@
         lockedOut = collectLastValue(underTest.isLockedOut)
         canFaceAuthRun = collectLastValue(underTest.canRunFaceAuth)
         authenticated = collectLastValue(underTest.isAuthenticated)
+        bypassEnabled = collectLastValue(underTest.isBypassEnabled)
         fakeUserRepository.setSelectedUserInfo(primaryUser)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
new file mode 100644
index 0000000..3d1d2f4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
@@ -0,0 +1,292 @@
+/*
+ *  Copyright (C) 2023 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.os.Handler
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.FaceAuthUiEvent
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.dump.logcatLogBuffer
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.DismissCallbackRegistry
+import com.android.systemui.keyguard.data.BouncerView
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.log.FaceAuthenticationLogger
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardFaceAuthInteractorTest : SysuiTestCase() {
+
+    private lateinit var underTest: SystemUIKeyguardFaceAuthInteractor
+    private lateinit var testScope: TestScope
+    private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
+    private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
+    private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+    private lateinit var faceAuthRepository: FakeDeviceEntryFaceAuthRepository
+
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        val scheduler = TestCoroutineScheduler()
+        val dispatcher = StandardTestDispatcher(scheduler)
+        testScope = TestScope(dispatcher)
+        val featureFlags = FakeFeatureFlags()
+        featureFlags.set(Flags.FACE_AUTH_REFACTOR, true)
+        bouncerRepository = FakeKeyguardBouncerRepository()
+        faceAuthRepository = FakeDeviceEntryFaceAuthRepository()
+        keyguardTransitionRepository = FakeKeyguardTransitionRepository()
+        keyguardTransitionInteractor = KeyguardTransitionInteractor(keyguardTransitionRepository)
+
+        underTest =
+            SystemUIKeyguardFaceAuthInteractor(
+                testScope.backgroundScope,
+                dispatcher,
+                faceAuthRepository,
+                PrimaryBouncerInteractor(
+                    bouncerRepository,
+                    mock(BouncerView::class.java),
+                    mock(Handler::class.java),
+                    mock(KeyguardStateController::class.java),
+                    mock(KeyguardSecurityModel::class.java),
+                    mock(PrimaryBouncerCallbackInteractor::class.java),
+                    mock(FalsingCollector::class.java),
+                    mock(DismissCallbackRegistry::class.java),
+                    context,
+                    keyguardUpdateMonitor,
+                    mock(KeyguardBypassController::class.java),
+                ),
+                AlternateBouncerInteractor(
+                    mock(StatusBarStateController::class.java),
+                    mock(KeyguardStateController::class.java),
+                    bouncerRepository,
+                    mock(BiometricSettingsRepository::class.java),
+                    FakeDeviceEntryFingerprintAuthRepository(),
+                    FakeSystemClock(),
+                ),
+                keyguardTransitionInteractor,
+                featureFlags,
+                FaceAuthenticationLogger(logcatLogBuffer("faceAuthBuffer")),
+                keyguardUpdateMonitor,
+            )
+    }
+
+    @Test
+    fun faceAuthIsRequestedWhenLockscreenBecomesVisibleFromOffState() =
+        testScope.runTest {
+            underTest.start()
+
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    KeyguardState.OFF,
+                    KeyguardState.LOCKSCREEN,
+                    transitionState = TransitionState.STARTED
+                )
+            )
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(
+                    Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED, true)
+                )
+        }
+
+    @Test
+    fun faceAuthIsRequestedWhenLockscreenBecomesVisibleFromAodState() =
+        testScope.runTest {
+            underTest.start()
+
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    KeyguardState.AOD,
+                    KeyguardState.LOCKSCREEN,
+                    transitionState = TransitionState.STARTED
+                )
+            )
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(
+                    Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED, true)
+                )
+        }
+
+    @Test
+    fun faceAuthIsRequestedWhenLockscreenBecomesVisibleFromDozingState() =
+        testScope.runTest {
+            underTest.start()
+
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    KeyguardState.DOZING,
+                    KeyguardState.LOCKSCREEN,
+                    transitionState = TransitionState.STARTED
+                )
+            )
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(
+                    Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED, true)
+                )
+        }
+
+    @Test
+    fun faceAuthIsRequestedWhenPrimaryBouncerIsVisible() =
+        testScope.runTest {
+            underTest.start()
+
+            bouncerRepository.setPrimaryShow(false)
+            runCurrent()
+
+            bouncerRepository.setPrimaryShow(true)
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN, true))
+        }
+
+    @Test
+    fun faceAuthIsRequestedWhenAlternateBouncerIsVisible() =
+        testScope.runTest {
+            underTest.start()
+
+            bouncerRepository.setAlternateVisible(false)
+            runCurrent()
+
+            bouncerRepository.setAlternateVisible(true)
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(
+                    Pair(
+                        FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN,
+                        false
+                    )
+                )
+        }
+
+    @Test
+    fun faceAuthIsRequestedWhenUdfpsSensorTouched() =
+        testScope.runTest {
+            underTest.start()
+
+            underTest.onUdfpsSensorTouched()
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_UDFPS_POINTER_DOWN, false))
+        }
+
+    @Test
+    fun faceAuthIsRequestedWhenOnAssistantTriggeredOnLockScreen() =
+        testScope.runTest {
+            underTest.start()
+
+            underTest.onAssistantTriggeredOnLockScreen()
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(
+                    Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED, true)
+                )
+        }
+
+    @Test
+    fun faceAuthIsRequestedWhenDeviceLifted() =
+        testScope.runTest {
+            underTest.start()
+
+            underTest.onDeviceLifted()
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(
+                    Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED, true)
+                )
+        }
+
+    @Test
+    fun faceAuthIsRequestedWhenQsExpansionStared() =
+        testScope.runTest {
+            underTest.start()
+
+            underTest.onQsExpansionStared()
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED, true))
+        }
+
+    @Test
+    fun faceAuthIsRequestedWhenNotificationPanelClicked() =
+        testScope.runTest {
+            underTest.start()
+
+            underTest.onNotificationPanelClicked()
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(
+                    Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED, true)
+                )
+        }
+
+    @Test
+    fun faceAuthIsRequestedWhenSwipeUpOnBouncer() =
+        testScope.runTest {
+            underTest.start()
+
+            underTest.onSwipeUpOnBouncer()
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, false))
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/util/MediaDataUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/util/MediaDataUtilsTest.kt
new file mode 100644
index 0000000..86f3062
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/util/MediaDataUtilsTest.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.media.controls.util
+
+import android.testing.AndroidTestingRunner
+import android.util.Pair as APair
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class MediaDataUtilsTest : SysuiTestCase() {
+
+    @Test
+    fun testScaleFactor_zeroInput_returnsZero() {
+        val input = APair(0, 0)
+        val target = APair(100, 100)
+
+        val scale = MediaDataUtils.getScaleFactor(input, target)
+        assertThat(scale).isEqualTo(0f)
+    }
+
+    @Test
+    fun testScaleFactor_tooWide_scaleDown() {
+        val input = APair(400, 200)
+        val target = APair(100, 100)
+
+        val scale = MediaDataUtils.getScaleFactor(input, target)
+        assertThat(scale).isEqualTo(0.5f)
+    }
+
+    @Test
+    fun testScaleFactor_tooTall_scaleDown() {
+        val input = APair(200, 400)
+        val target = APair(100, 100)
+
+        val scale = MediaDataUtils.getScaleFactor(input, target)
+        assertThat(scale).isEqualTo(0.5f)
+    }
+
+    @Test
+    fun testScaleFactor_lessWide_scaleUp() {
+        val input = APair(50, 100)
+        val target = APair(100, 100)
+
+        val scale = MediaDataUtils.getScaleFactor(input, target)
+        assertThat(scale).isEqualTo(2f)
+    }
+
+    @Test
+    fun testScaleFactor_lessTall_scaleUp() {
+        val input = APair(100, 50)
+        val target = APair(100, 100)
+
+        val scale = MediaDataUtils.getScaleFactor(input, target)
+        assertThat(scale).isEqualTo(2f)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index aa92177..e4d8b25 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -46,7 +46,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
 import com.android.internal.util.CollectionUtils;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
@@ -117,8 +116,6 @@
     @Mock
     private CustomTile mCustomTile;
     @Mock
-    private UiEventLogger mUiEventLogger;
-    @Mock
     private UserTracker mUserTracker;
     private SecureSettings mSecureSettings;
     @Mock
@@ -164,7 +161,7 @@
         saveSetting("");
         mQSTileHost = new TestQSTileHost(mContext, mDefaultFactory, mMainExecutor,
                 mPluginManager, mTunerService, mAutoTiles, mCentralSurfaces,
-                mQSLogger, mUiEventLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister,
+                mQSLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister,
                 mTileLifecycleManagerFactory, mUserFileManager, mFeatureFlags);
 
         mSecureSettings.registerContentObserverForUser(SETTING, new ContentObserver(null) {
@@ -684,14 +681,14 @@
                 QSFactory defaultFactory, Executor mainExecutor,
                 PluginManager pluginManager, TunerService tunerService,
                 Provider<AutoTileManager> autoTiles,
-                CentralSurfaces centralSurfaces, QSLogger qsLogger, UiEventLogger uiEventLogger,
+                CentralSurfaces centralSurfaces, QSLogger qsLogger,
                 UserTracker userTracker, SecureSettings secureSettings,
                 CustomTileStatePersister customTileStatePersister,
                 TileLifecycleManager.Factory tileLifecycleManagerFactory,
                 UserFileManager userFileManager, FeatureFlags featureFlags) {
             super(context, defaultFactory, mainExecutor, pluginManager,
                     tunerService, autoTiles,  Optional.of(centralSurfaces), qsLogger,
-                    uiEventLogger, userTracker, secureSettings, customTileStatePersister,
+                    userTracker, secureSettings, customTileStatePersister,
                     tileLifecycleManagerFactory, userFileManager, featureFlags);
         }
 
@@ -710,6 +707,7 @@
         protected TestTile(QSHost host) {
             super(
                     host,
+                    mock(QsEventLogger.class),
                     mock(Looper.class),
                     mock(Handler.class),
                     new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QsEventLoggerFake.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QsEventLoggerFake.kt
new file mode 100644
index 0000000..40aa260
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QsEventLoggerFake.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.qs
+
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.InstanceIdSequenceFake
+
+class QsEventLoggerFake(
+    uiEventLogger: UiEventLoggerFake,
+    private val instanceIdSequence: InstanceIdSequenceFake,
+) : QsEventLogger, UiEventLogger by uiEventLogger {
+
+    val lastInstanceId: Int
+        get() = instanceIdSequence.lastInstanceId
+
+    override fun getNewInstanceId(): InstanceId {
+        return instanceIdSequence.newInstanceId()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
index ac106ef..198ed4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
@@ -41,6 +41,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.settings.FakeDisplayTracker
 import com.android.systemui.util.mockito.any
@@ -56,12 +57,12 @@
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -89,6 +90,7 @@
     @Mock private lateinit var applicationInfo: ApplicationInfo
     @Mock private lateinit var serviceInfo: ServiceInfo
     @Mock private lateinit var customTileStatePersister: CustomTileStatePersister
+    @Mock private lateinit var uiEventLogger: QsEventLogger
 
     private var displayTracker = FakeDisplayTracker(mContext)
     private lateinit var customTile: CustomTile
@@ -115,6 +117,7 @@
 
         customTileBuilder = CustomTile.Builder(
                 { tileHost },
+                uiEventLogger,
                 testableLooper.looper,
                 Handler(testableLooper.looper),
                 FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
index 36549fb..962b537 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -61,6 +61,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+import com.android.systemui.InstanceIdSequenceFake;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.plugins.ActivityStarter;
@@ -69,6 +70,8 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSEvent;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
+import com.android.systemui.qs.QsEventLoggerFake;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.statusbar.StatusBarState;
 
@@ -106,7 +109,8 @@
     private ActivityStarter mActivityStarter;
 
     private UiEventLoggerFake mUiEventLoggerFake;
-    private InstanceId mInstanceId = InstanceId.fakeInstanceId(5);
+    private QsEventLoggerFake mQsEventLoggerFake;
+    private InstanceId mInstanceId;
 
     @Captor
     private ArgumentCaptor<LogMaker> mLogCaptor;
@@ -115,18 +119,29 @@
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
         mTestableLooper = TestableLooper.get(this);
+
         mUiEventLoggerFake = new UiEventLoggerFake();
+        mQsEventLoggerFake =
+                new QsEventLoggerFake(mUiEventLoggerFake, new InstanceIdSequenceFake(10));
         when(mHost.indexOf(SPEC)).thenReturn(POSITION);
         when(mHost.getContext()).thenReturn(mContext);
-        when(mHost.getUiEventLogger()).thenReturn(mUiEventLoggerFake);
-        when(mHost.getNewInstanceId()).thenReturn(mInstanceId);
 
         Handler mainHandler = new Handler(mTestableLooper.getLooper());
 
-        mTile = new TileImpl(mHost, mTestableLooper.getLooper(), mainHandler, mFalsingManager,
-                mMetricsLogger, mStatusBarStateController, mActivityStarter, mQsLogger);
+        mTile = new TileImpl(
+                mHost,
+                mQsEventLoggerFake,
+                mTestableLooper.getLooper(),
+                mainHandler,
+                mFalsingManager,
+                mMetricsLogger,
+                mStatusBarStateController,
+                mActivityStarter,
+                mQsLogger
+        );
         mTile.initialize();
         mTestableLooper.processAllMessages();
+        mInstanceId = InstanceId.fakeInstanceId(mQsEventLoggerFake.getLastInstanceId());
 
         mTile.setTileSpec(SPEC);
     }
@@ -507,6 +522,7 @@
 
         protected TileImpl(
                 QSHost host,
+                QsEventLogger uiEventLogger,
                 Looper backgroundLooper,
                 Handler mainHandler,
                 FalsingManager falsingManager,
@@ -515,7 +531,7 @@
                 ActivityStarter activityStarter,
                 QSLogger qsLogger
         ) {
-            super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+            super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                     statusBarStateController, activityStarter, qsLogger);
             getState().state = Tile.STATE_ACTIVE;
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
index 5e0190b..c60cecb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
@@ -22,8 +22,6 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
@@ -32,6 +30,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.settings.UserTracker
@@ -68,20 +67,21 @@
     private lateinit var mGlobalSettings: GlobalSettings
     @Mock
     private lateinit var mUserTracker: UserTracker
+    @Mock
+    private lateinit var mUiEventLogger: QsEventLogger
     private lateinit var mTestableLooper: TestableLooper
     private lateinit var mTile: AirplaneModeTile
 
-    private val mUiEventLogger: UiEventLogger = UiEventLoggerFake()
-
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         mTestableLooper = TestableLooper.get(this)
         Mockito.`when`(mHost.context).thenReturn(mContext)
-        Mockito.`when`(mHost.uiEventLogger).thenReturn(mUiEventLogger)
         Mockito.`when`(mHost.userContext).thenReturn(mContext)
 
-        mTile = AirplaneModeTile(mHost,
+        mTile = AirplaneModeTile(
+            mHost,
+            mUiEventLogger,
             mTestableLooper.looper,
             Handler(mTestableLooper.looper),
             FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt
index f1e3e8a..52b8455 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt
@@ -9,12 +9,12 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.NextAlarmController
@@ -28,8 +28,8 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -52,7 +52,7 @@
     @Mock
     private lateinit var nextAlarmController: NextAlarmController
     @Mock
-    private lateinit var uiEventLogger: UiEventLogger
+    private lateinit var uiEventLogger: QsEventLogger
     @Mock
     private lateinit var pendingIntent: PendingIntent
     @Captor
@@ -67,10 +67,10 @@
         testableLooper = TestableLooper.get(this)
 
         `when`(qsHost.context).thenReturn(mContext)
-        `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
 
         tile = AlarmTile(
             qsHost,
+            uiEventLogger,
             testableLooper.looper,
             Handler(testableLooper.looper),
             FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
index a5c0004..ff6814c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.statusbar.policy.BatteryController
@@ -43,10 +44,10 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
@@ -63,6 +64,8 @@
     @Mock
     private lateinit var qsHost: QSHost
     @Mock
+    private lateinit var uiEventLogger: QsEventLogger
+    @Mock
     private lateinit var metricsLogger: MetricsLogger
     @Mock
     private lateinit var statusBarStateController: StatusBarStateController
@@ -90,6 +93,7 @@
 
         tile = BatterySaverTile(
                 qsHost,
+                uiEventLogger,
                 testableLooper.looper,
                 Handler(testableLooper.looper),
                 FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
index 2e77de2..5e7f68c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
@@ -9,7 +9,6 @@
 import android.testing.TestableLooper.RunWithLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.settingslib.Utils
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
 import com.android.systemui.R
@@ -20,6 +19,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.statusbar.policy.BluetoothController
@@ -49,8 +49,8 @@
     @Mock private lateinit var statusBarStateController: StatusBarStateController
     @Mock private lateinit var activityStarter: ActivityStarter
     @Mock private lateinit var bluetoothController: BluetoothController
+    @Mock private lateinit var uiEventLogger: QsEventLogger
 
-    private val uiEventLogger = UiEventLoggerFake()
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: FakeBluetoothTile
 
@@ -60,11 +60,11 @@
         testableLooper = TestableLooper.get(this)
 
         whenever(qsHost.context).thenReturn(mContext)
-        whenever(qsHost.uiEventLogger).thenReturn(uiEventLogger)
 
         tile =
             FakeBluetoothTile(
                 qsHost,
+                uiEventLogger,
                 testableLooper.looper,
                 Handler(testableLooper.looper),
                 falsingManager,
@@ -211,6 +211,7 @@
 
     private class FakeBluetoothTile(
         qsHost: QSHost,
+        uiEventLogger: QsEventLogger,
         backgroundLooper: Looper,
         mainHandler: Handler,
         falsingManager: FalsingManager,
@@ -222,6 +223,7 @@
     ) :
         BluetoothTile(
             qsHost,
+            uiEventLogger,
             backgroundLooper,
             mainHandler,
             falsingManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
index 4193854..70d82fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
@@ -21,8 +21,6 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
@@ -30,6 +28,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLoggerFake
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController
@@ -67,19 +66,21 @@
     private lateinit var privacyController: IndividualSensorPrivacyController
     @Mock
     private lateinit var keyguardStateController: KeyguardStateController
+    @Mock
+    private lateinit var uiEventLogger: QsEventLoggerFake
 
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: CameraToggleTile
-    private val uiEventLogger: UiEventLogger = UiEventLoggerFake()
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
         whenever(host.context).thenReturn(mContext)
-        whenever(host.uiEventLogger).thenReturn(uiEventLogger)
 
-        tile = CameraToggleTile(host,
+        tile = CameraToggleTile(
+                host,
+                uiEventLogger,
                 testableLooper.looper,
                 Handler(testableLooper.looper),
                 metricsLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
index 64fd09d5..93ed994 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
@@ -43,6 +43,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.statusbar.connectivity.IconState;
 import com.android.systemui.statusbar.connectivity.NetworkController;
@@ -94,6 +95,8 @@
     private QSLogger mQSLogger;
     @Mock
     private DialogLaunchAnimator mDialogLaunchAnimator;
+    @Mock
+    private QsEventLogger mUiEventLogger;
 
     private TestableLooper mTestableLooper;
     private CastTile mCastTile;
@@ -107,6 +110,7 @@
 
         mCastTile = new CastTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java
index 13c30e9..2250ef3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java
@@ -32,12 +32,12 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.settings.FakeSettings;
@@ -67,7 +67,7 @@
     @Mock
     private QSLogger mQSLogger;
     @Mock
-    private UiEventLogger mUiEventLogger;
+    private QsEventLogger mUiEventLogger;
     @Mock
     private UserTracker mUserTracker;
 
@@ -83,10 +83,10 @@
         mTestableLooper = TestableLooper.get(this);
 
         when(mHost.getContext()).thenReturn(mContext);
-        when(mHost.getUiEventLogger()).thenReturn(mUiEventLogger);
 
         mTile = new ColorCorrectionTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
index ff27e02..2e02bbe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
@@ -32,7 +32,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
@@ -40,6 +39,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.settings.UserTracker;
@@ -72,7 +72,7 @@
     @Mock
     private QSLogger mQSLogger;
     @Mock
-    private UiEventLogger mUiEventLogger;
+    private QsEventLogger mUiEventLogger;
     @Mock
     private UserTracker mUserTracker;
 
@@ -88,10 +88,10 @@
         mTestableLooper = TestableLooper.get(this);
 
         when(mHost.getContext()).thenReturn(mContext);
-        when(mHost.getUiEventLogger()).thenReturn(mUiEventLogger);
 
         mTile = new ColorInversionTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
index b048643..176b33f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
@@ -21,7 +21,6 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogLaunchAnimator
@@ -30,6 +29,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.statusbar.policy.DataSaverController
@@ -57,8 +57,8 @@
     @Mock private lateinit var activityStarter: ActivityStarter
     @Mock private lateinit var dataSaverController: DataSaverController
     @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+    @Mock private lateinit var uiEventLogger: QsEventLogger
 
-    private val uiEventLogger = UiEventLoggerFake()
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: DataSaverTile
 
@@ -68,11 +68,11 @@
         testableLooper = TestableLooper.get(this)
 
         Mockito.`when`(mHost.context).thenReturn(mContext)
-        Mockito.`when`(mHost.uiEventLogger).thenReturn(uiEventLogger)
 
         tile =
             DataSaverTile(
                 mHost,
+                uiEventLogger,
                 testableLooper.looper,
                 Handler(testableLooper.looper),
                 falsingManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
index b51c378..1346069 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
@@ -27,7 +27,6 @@
 import androidx.lifecycle.LifecycleOwner
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityLaunchAnimator
@@ -44,6 +43,7 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.util.mockito.any
@@ -52,6 +52,7 @@
 import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.util.settings.SecureSettings
 import com.google.common.truth.Truth.assertThat
+import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -59,15 +60,14 @@
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.Captor
 import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.doNothing
 import org.mockito.Mockito.nullable
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
 import java.util.Optional
-import org.junit.After
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -95,7 +95,7 @@
     @Mock
     private lateinit var serviceInfo: ControlsServiceInfo
     @Mock
-    private lateinit var uiEventLogger: UiEventLogger
+    private lateinit var uiEventLogger: QsEventLogger
     @Captor
     private lateinit var listingCallbackCaptor:
             ArgumentCaptor<ControlsListingController.ControlsListingCallback>
@@ -118,7 +118,6 @@
         spiedContext = spy(mContext)
         doNothing().`when`(spiedContext).startActivity(any(Intent::class.java))
         `when`(qsHost.context).thenReturn(spiedContext)
-        `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
         `when`(controlsComponent.isEnabled()).thenReturn(true)
         `when`(controlsController.getPreferredSelection())
                 .thenReturn(SelectedItem.StructureItem(
@@ -399,6 +398,7 @@
     private fun createTile(): DeviceControlsTile {
         return DeviceControlsTile(
                 qsHost,
+                uiEventLogger,
                 testableLooper.looper,
                 Handler(testableLooper.looper),
                 FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
index 6c0904e..f0e4e3a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
@@ -28,7 +28,6 @@
 import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogLaunchAnimator
@@ -37,6 +36,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.statusbar.policy.ZenModeController
@@ -46,7 +46,6 @@
 import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.util.settings.SecureSettings
 import com.google.common.truth.Truth.assertThat
-import java.io.File
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
@@ -55,8 +54,9 @@
 import org.mockito.Mockito.anyBoolean
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
+import java.io.File
+import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -84,7 +84,7 @@
     private lateinit var qsLogger: QSLogger
 
     @Mock
-    private lateinit var uiEventLogger: UiEventLogger
+    private lateinit var uiEventLogger: QsEventLogger
 
     @Mock
     private lateinit var zenModeController: ZenModeController
@@ -109,7 +109,6 @@
         secureSettings = FakeSettings()
 
         whenever(qsHost.userId).thenReturn(DEFAULT_USER)
-        whenever(qsHost.uiEventLogger).thenReturn(uiEventLogger)
 
         val wrappedContext = object : ContextWrapper(context) {
             override fun getSharedPreferences(file: File?, mode: Int): SharedPreferences {
@@ -120,6 +119,7 @@
 
         tile = DndTile(
             qsHost,
+            uiEventLogger,
             testableLooper.looper,
             Handler(testableLooper.looper),
             FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java
index 7d41aa6..f231c6e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java
@@ -48,6 +48,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.settings.UserTracker;
@@ -83,6 +84,8 @@
     private BroadcastDispatcher mBroadcastDispatcher;
     @Mock
     private UserTracker mUserTracker;
+    @Mock
+    private QsEventLogger mUiEventLogger;
 
     private TestableLooper mTestableLooper;
 
@@ -258,6 +261,7 @@
             boolean dreamOnlyEnabledForSystemUser) {
         return new DreamTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt
index 692a644..73aa699 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt
@@ -6,7 +6,6 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
@@ -14,6 +13,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.statusbar.policy.FlashlightController
@@ -45,7 +45,8 @@
 
     @Mock private lateinit var flashlightController: FlashlightController
 
-    private val uiEventLogger = UiEventLoggerFake()
+    @Mock private lateinit var uiEventLogger: QsEventLogger
+
     private val falsingManager = FalsingManagerFake()
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: FlashlightTile
@@ -56,11 +57,11 @@
         testableLooper = TestableLooper.get(this)
 
         Mockito.`when`(qsHost.context).thenReturn(mockContext)
-        Mockito.`when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
 
         tile =
             FlashlightTile(
                 qsHost,
+                uiEventLogger,
                 testableLooper.looper,
                 Handler(testableLooper.looper),
                 falsingManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
index eeebd4f..1d6f225 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
@@ -23,7 +23,6 @@
 import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.classifier.FalsingManagerFake
@@ -31,7 +30,8 @@
 import com.android.systemui.flags.Flags
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.qs.QSTileHost
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
@@ -52,13 +52,13 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
 class FontScalingTileTest : SysuiTestCase() {
-    @Mock private lateinit var qsHost: QSTileHost
+    @Mock private lateinit var qsHost: QSHost
     @Mock private lateinit var metricsLogger: MetricsLogger
     @Mock private lateinit var statusBarStateController: StatusBarStateController
     @Mock private lateinit var activityStarter: ActivityStarter
     @Mock private lateinit var qsLogger: QSLogger
     @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
-    @Mock private lateinit var uiEventLogger: UiEventLogger
+    @Mock private lateinit var uiEventLogger: QsEventLogger
 
     private lateinit var testableLooper: TestableLooper
     private lateinit var fontScalingTile: FontScalingTile
@@ -70,11 +70,11 @@
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
         `when`(qsHost.getContext()).thenReturn(mContext)
-        `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
 
         fontScalingTile =
             FontScalingTile(
                 qsHost,
+                uiEventLogger,
                 testableLooper.looper,
                 Handler(testableLooper.looper),
                 FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java
index 959e750..73f61d06 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java
@@ -38,6 +38,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.DataSaverController;
@@ -66,6 +67,8 @@
     private HotspotController mHotspotController;
     @Mock
     private DataSaverController mDataSaverController;
+    @Mock
+    private QsEventLogger mUiEventLogger;
 
     private TestableLooper mTestableLooper;
     private HotspotTile mTile;
@@ -80,6 +83,7 @@
 
         mTile = new HotspotTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
index adfd7f7..7957c6a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
@@ -35,6 +35,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
@@ -63,6 +64,8 @@
     private AccessPointController mAccessPointController;
     @Mock
     private InternetDialogFactory mInternetDialogFactory;
+    @Mock
+    private QsEventLogger mUiEventLogger;
 
     private TestableLooper mTestableLooper;
     private InternetTile mTile;
@@ -76,6 +79,7 @@
 
         mTile = new InternetTile(
             mHost,
+            mUiEventLogger,
             mTestableLooper.getLooper(),
             new Handler(mTestableLooper.getLooper()),
             new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
index 3642e87..0bf0b38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
@@ -22,7 +22,6 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
@@ -30,6 +29,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
 import com.android.systemui.qs.tileimpl.QSTileImpl
@@ -43,8 +43,8 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
@@ -71,8 +71,9 @@
     private lateinit var keyguardStateController: KeyguardStateController
     @Mock
     private lateinit var panelInteractor: PanelInteractor
+    @Mock
+    private lateinit var uiEventLogger: QsEventLogger
 
-    private val uiEventLogger = UiEventLoggerFake()
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: LocationTile
 
@@ -80,10 +81,11 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
-        `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
         `when`(qsHost.context).thenReturn(mockContext)
 
-        tile = LocationTile(qsHost,
+        tile = LocationTile(
+            qsHost,
+            uiEventLogger,
             testableLooper.looper,
             Handler(testableLooper.looper),
             falsingManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
index e2f64b2..ceff546 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
@@ -21,8 +21,6 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
@@ -30,6 +28,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController
@@ -67,19 +66,22 @@
     private lateinit var privacyController: IndividualSensorPrivacyController
     @Mock
     private lateinit var keyguardStateController: KeyguardStateController
+    @Mock
+    private lateinit var uiEventLogger: QsEventLogger
 
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: MicrophoneToggleTile
-    private val uiEventLogger: UiEventLogger = UiEventLoggerFake()
+
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
         whenever(host.context).thenReturn(mContext)
-        whenever(host.uiEventLogger).thenReturn(uiEventLogger)
 
-        tile = MicrophoneToggleTile(host,
+        tile = MicrophoneToggleTile(
+                host,
+                uiEventLogger,
                 testableLooper.looper,
                 Handler(testableLooper.looper),
                 metricsLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java
index c7dae83..763a7e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java
@@ -37,6 +37,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 
 import org.junit.After;
@@ -70,6 +71,8 @@
     private QSLogger mQSLogger;
     @Mock
     private BroadcastDispatcher mBroadcastDispatcher;
+    @Mock
+    private QsEventLogger mUiEventLogger;
 
     private TestableLooper mTestableLooper;
     private NfcTile mNfcTile;
@@ -84,6 +87,7 @@
 
         mNfcTile = new NfcTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt
index 04af69c..6c8f76b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt
@@ -23,8 +23,6 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
@@ -33,6 +31,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.statusbar.policy.LocationController
@@ -43,8 +42,8 @@
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
 
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -68,17 +67,18 @@
 
     @Mock private lateinit var mNightDisplayListener: NightDisplayListener
 
+    @Mock private lateinit var mUiEventLogger: QsEventLogger
+
     private lateinit var mTestableLooper: TestableLooper
     private lateinit var mTile: NightDisplayTile
 
-    private val mUiEventLogger: UiEventLogger = UiEventLoggerFake()
+
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         mTestableLooper = TestableLooper.get(this)
         whenever(mHost.context).thenReturn(mContext)
-        whenever(mHost.uiEventLogger).thenReturn(mUiEventLogger)
         whenever(mHost.userContext).thenReturn(mContext)
         whenever(mNightDisplayListenerBuilder.setUser(anyInt()))
             .thenReturn(mNightDisplayListenerBuilder)
@@ -87,6 +87,7 @@
         mTile =
             NightDisplayTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.looper,
                 Handler(mTestableLooper.looper),
                 FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java
index 652c138..c391153 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java
@@ -33,6 +33,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.settings.SecureSettings;
@@ -65,6 +66,8 @@
     private UserTracker mUserTracker;
     @Mock
     private SecureSettings mSecureSettings;
+    @Mock
+    private QsEventLogger mUiEventLogger;
 
     private TestableLooper mTestableLooper;
     private OneHandedModeTile mTile;
@@ -78,6 +81,7 @@
 
         mTile = spy(new OneHandedModeTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
index 3125d45..6f2d904 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
@@ -30,8 +30,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
@@ -40,6 +38,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 
@@ -64,7 +63,8 @@
     private ActivityStarter mActivityStarter;
     @Mock
     private QSLogger mQSLogger;
-    private UiEventLogger mUiEventLogger = new UiEventLoggerFake();
+    @Mock
+    private QsEventLogger mUiEventLogger;
     @Mock
     private QRCodeScannerController mController;
 
@@ -79,6 +79,7 @@
 
         mTile = new QRCodeScannerTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
index 596df78..b089e38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
@@ -58,8 +58,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
@@ -67,6 +65,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -109,7 +108,8 @@
     private ActivityStarter mActivityStarter;
     @Mock
     private QSLogger mQSLogger;
-    private UiEventLogger mUiEventLogger = new UiEventLoggerFake();
+    @Mock
+    private QsEventLogger mUiEventLogger;
     @Mock
     private QuickAccessWalletClient mQuickAccessWalletClient;
     @Mock
@@ -136,7 +136,6 @@
 
         doNothing().when(mSpiedContext).startActivity(any(Intent.class));
         when(mHost.getContext()).thenReturn(mSpiedContext);
-        when(mHost.getUiEventLogger()).thenReturn(mUiEventLogger);
         when(mQuickAccessWalletClient.getServiceLabel()).thenReturn(LABEL);
         when(mQuickAccessWalletClient.getTileIcon()).thenReturn(mTileIcon);
         when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(true);
@@ -146,6 +145,7 @@
 
         mTile = new QuickAccessWalletTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
index 7913628..d244594 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
@@ -39,6 +39,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.ReduceBrightColorsController;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -69,6 +70,8 @@
     private UserTracker mUserTracker;
     @Mock
     private ReduceBrightColorsController mReduceBrightColorsController;
+    @Mock
+    private QsEventLogger mUiEventLogger;
 
     private TestableLooper mTestableLooper;
     private ReduceBrightColorsTile mTile;
@@ -85,6 +88,7 @@
                 true,
                 mReduceBrightColorsController,
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
index 5b94cfe..e106741 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
@@ -39,6 +39,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -87,6 +88,8 @@
     DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController;
     @Mock
     RotationPolicyWrapper mRotationPolicyWrapper;
+    @Mock
+    QsEventLogger mUiEventLogger;
 
     private RotationLockController mController;
     private TestableLooper mTestableLooper;
@@ -105,6 +108,7 @@
 
         mLockTile = new RotationLockTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
index d9ed1a2..fff2b8f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
@@ -44,6 +44,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -86,6 +87,8 @@
     private DialogLaunchAnimator mDialogLaunchAnimator;
     @Mock
     private PanelInteractor mPanelInteractor;
+    @Mock
+    private QsEventLogger mUiEventLogger;
 
     private TestableLooper mTestableLooper;
     private ScreenRecordTile mTile;
@@ -100,6 +103,7 @@
 
         mTile = new ScreenRecordTile(
                 mHost,
+                mUiEventLogger,
                 mTestableLooper.getLooper(),
                 new Handler(mTestableLooper.getLooper()),
                 new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
index b556571..79147e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
@@ -25,7 +25,6 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
@@ -33,6 +32,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.statusbar.policy.BatteryController
@@ -63,8 +63,8 @@
     @Mock private lateinit var configurationController: ConfigurationController
     @Mock private lateinit var batteryController: BatteryController
     @Mock private lateinit var locationController: LocationController
+    @Mock private lateinit var uiEventLogger: QsEventLogger
 
-    private val uiEventLogger = UiEventLoggerFake()
     private val falsingManager = FalsingManagerFake()
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: UiModeNightTile
@@ -81,11 +81,11 @@
         `when`(qsHost.userContext).thenReturn(mContext)
         `when`(mockContext.resources).thenReturn(resources)
         `when`(resources.configuration).thenReturn(configuration)
-        `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
 
         tile =
             UiModeNightTile(
                 qsHost,
+                uiEventLogger,
                 testableLooper.looper,
                 Handler(testableLooper.looper),
                 falsingManager,
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 5ca3771..068d933 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -93,6 +93,7 @@
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
@@ -302,6 +303,7 @@
     protected ArgumentCaptor<NotificationStackScrollLayout.OnEmptySpaceClickListener>
             mEmptySpaceClickListenerCaptor;
     @Mock protected ActivityStarter mActivityStarter;
+    @Mock protected KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
 
     protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
     protected KeyguardInteractor mKeyguardInteractor;
@@ -599,7 +601,8 @@
                 mDumpManager,
                 mKeyuardLongPressViewModel,
                 mKeyguardInteractor,
-                mActivityStarter);
+                mActivityStarter,
+                mKeyguardFaceAuthInteractor);
         mNotificationPanelViewController.initDependencies(
                 mCentralSurfaces,
                 null,
@@ -665,7 +668,8 @@
                 mMetricsLogger,
                 mFeatureFlags,
                 mInteractionJankMonitor,
-                mShadeLog
+                mShadeLog,
+                mKeyguardFaceAuthInteractor
         );
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 2db9c97..600fb5c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -930,6 +930,7 @@
         mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
         mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
 
+        verify(mKeyguardFaceAuthInteractor).onNotificationPanelClicked();
         verify(mUpdateMonitor).requestFaceAuth(
                 FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
index d8ffe39..908f7cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
@@ -62,6 +62,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.media.controls.pipeline.MediaDataManager;
 import com.android.systemui.media.controls.ui.MediaHierarchyManager;
 import com.android.systemui.plugins.FalsingManager;
@@ -239,7 +240,8 @@
                 mMetricsLogger,
                 mFeatureFlags,
                 mInteractionJankMonitor,
-                mShadeLogger
+                mShadeLogger,
+                mock(KeyguardFaceAuthInteractor.class)
         );
 
         mFragmentListener = mQsController.getQsFragmentListener();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 569f90b..4438b98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -614,9 +614,8 @@
     public void onBiometricHelp_coEx_faceFailure() {
         createController();
 
-        // GIVEN unlocking with fingerprint is possible
-        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(anyInt()))
-                .thenReturn(true);
+        // GIVEN unlocking with fingerprint is possible and allowed
+        fingerprintUnlockIsPossibleAndAllowed();
 
         String message = "A message";
         mController.setVisible(true);
@@ -641,9 +640,8 @@
     public void onBiometricHelp_coEx_faceUnavailable() {
         createController();
 
-        // GIVEN unlocking with fingerprint is possible
-        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(anyInt()))
-                .thenReturn(true);
+        // GIVEN unlocking with fingerprint is possible and allowed
+        fingerprintUnlockIsPossibleAndAllowed();
 
         String message = "A message";
         mController.setVisible(true);
@@ -664,6 +662,35 @@
                 mContext.getString(R.string.keyguard_suggest_fingerprint));
     }
 
+
+    @Test
+    public void onBiometricHelp_coEx_faceUnavailable_fpNotAllowed() {
+        createController();
+
+        // GIVEN unlocking with fingerprint is possible but not allowed
+        setupFingerprintUnlockPossible(true);
+        when(mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed())
+                .thenReturn(false);
+
+        String message = "A message";
+        mController.setVisible(true);
+
+        // WHEN there's a face unavailable message
+        mController.getKeyguardCallback().onBiometricHelp(
+                BIOMETRIC_HELP_FACE_NOT_AVAILABLE,
+                message,
+                BiometricSourceType.FACE);
+
+        // THEN show sequential messages such as: 'face unlock unavailable' and
+        // 'try fingerprint instead'
+        verifyIndicationMessage(
+                INDICATION_TYPE_BIOMETRIC_MESSAGE,
+                message);
+        verifyIndicationMessage(
+                INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+                mContext.getString(R.string.keyguard_unlock));
+    }
+
     @Test
     public void onBiometricHelp_coEx_fpFailure_faceAlreadyUnlocked() {
         createController();
@@ -818,8 +845,7 @@
     @Test
     public void faceErrorTimeout_whenFingerprintEnrolled_doesNotShowMessage() {
         createController();
-        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                getCurrentUser())).thenReturn(true);
+        fingerprintUnlockIsPossibleAndAllowed();
         String message = "A message";
 
         mController.setVisible(true);
@@ -832,9 +858,8 @@
     public void sendFaceHelpMessages_fingerprintEnrolled() {
         createController();
 
-        // GIVEN fingerprint enrolled
-        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                getCurrentUser())).thenReturn(true);
+        // GIVEN unlocking with fingerprint is possible and allowed
+        fingerprintUnlockIsPossibleAndAllowed();
 
         // WHEN help messages received that are allowed to show
         final String helpString = "helpString";
@@ -859,9 +884,8 @@
     public void doNotSendMostFaceHelpMessages_fingerprintEnrolled() {
         createController();
 
-        // GIVEN fingerprint enrolled
-        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                getCurrentUser())).thenReturn(true);
+        // GIVEN unlocking with fingerprint is possible and allowed
+        fingerprintUnlockIsPossibleAndAllowed();
 
         // WHEN help messages received that aren't supposed to show
         final String helpString = "helpString";
@@ -886,9 +910,8 @@
     public void sendAllFaceHelpMessages_fingerprintNotEnrolled() {
         createController();
 
-        // GIVEN fingerprint NOT enrolled
-        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                getCurrentUser())).thenReturn(false);
+        // GIVEN fingerprint NOT possible
+        fingerprintUnlockIsNotPossible();
 
         // WHEN help messages received
         final Set<CharSequence> helpStrings = new HashSet<>();
@@ -917,9 +940,8 @@
     public void sendTooDarkFaceHelpMessages_onTimeout_noFpEnrolled() {
         createController();
 
-        // GIVEN fingerprint NOT enrolled
-        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                getCurrentUser())).thenReturn(false);
+        // GIVEN fingerprint not possible
+        fingerprintUnlockIsNotPossible();
 
         // WHEN help message received and deferred message is valid
         final String helpString = "helpMsg";
@@ -948,9 +970,8 @@
     public void sendTooDarkFaceHelpMessages_onTimeout_fingerprintEnrolled() {
         createController();
 
-        // GIVEN fingerprint enrolled
-        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                getCurrentUser())).thenReturn(true);
+        // GIVEN unlocking with fingerprint is possible and allowed
+        fingerprintUnlockIsPossibleAndAllowed();
 
         // WHEN help message received and deferredMessage is valid
         final String helpString = "helpMsg";
@@ -1500,7 +1521,7 @@
     @Test
     public void onBiometricError_faceLockedOutFirstTimeAndFpAllowed_showsTheFpFollowupMessage() {
         createController();
-        fingerprintUnlockIsPossible();
+        fingerprintUnlockIsPossibleAndAllowed();
         onFaceLockoutError("first lockout");
 
         verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
@@ -1559,7 +1580,7 @@
     @Test
     public void onBiometricError_faceLockedOutAgainAndFpAllowed_showsTheFpFollowupMessage() {
         createController();
-        fingerprintUnlockIsPossible();
+        fingerprintUnlockIsPossibleAndAllowed();
         onFaceLockoutError("first lockout");
         clearInvocations(mRotateTextViewController);
 
@@ -1668,7 +1689,7 @@
     public void onBiometricError_screenIsTurningOn_faceLockedOutFpIsAvailable_showsMessage() {
         createController();
         screenIsTurningOn();
-        fingerprintUnlockIsPossible();
+        fingerprintUnlockIsPossibleAndAllowed();
 
         onFaceLockoutError("lockout error");
         verifyNoMoreInteractions(mRotateTextViewController);
@@ -1746,8 +1767,9 @@
         setupFingerprintUnlockPossible(false);
     }
 
-    private void fingerprintUnlockIsPossible() {
+    private void fingerprintUnlockIsPossibleAndAllowed() {
         setupFingerprintUnlockPossible(true);
+        when(mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed()).thenReturn(true);
     }
 
     private void setupFingerprintUnlockPossible(boolean possible) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
index 7b59cc2..08a9f31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -22,7 +22,7 @@
 import android.testing.TestableLooper.RunWithLooper
 import android.view.View
 import android.widget.FrameLayout
-import androidx.core.animation.AnimatorTestRule2
+import androidx.core.animation.AnimatorTestRule
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
@@ -70,7 +70,7 @@
     private lateinit var systemStatusAnimationScheduler: SystemStatusAnimationScheduler
     private val fakeFeatureFlags = FakeFeatureFlags()
 
-    @get:Rule val animatorTestRule = AnimatorTestRule2()
+    @get:Rule val animatorTestRule = AnimatorTestRule()
 
     @Before
     fun setup() {
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 be3b723..aff705f 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
@@ -18,7 +18,7 @@
 
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
-import androidx.core.animation.AnimatorTestRule2
+import androidx.core.animation.AnimatorTestRule
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
@@ -51,7 +51,7 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
 
-    @get:Rule val animatorTestRule = AnimatorTestRule2()
+    @get:Rule val animatorTestRule = AnimatorTestRule()
 
     private val dumpManager: DumpManager = mock()
     private val headsUpManager: HeadsUpManager = mock()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
index 8109e24..c2a2a40 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
@@ -25,6 +25,10 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.advanceTimeBy
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.notification.NotifPipelineFlags
@@ -69,6 +73,7 @@
     private val headsUpManager: HeadsUpManager = mock()
     private val keyguardNotifVisibilityProvider: KeyguardNotificationVisibilityProvider = mock()
     private val keyguardRepository = FakeKeyguardRepository()
+    private val keyguardTransitionRepository = FakeKeyguardTransitionRepository()
     private val notifPipelineFlags: NotifPipelineFlags = mock()
     private val notifPipeline: NotifPipeline = mock()
     private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider = mock()
@@ -118,6 +123,33 @@
     }
 
     @Test
+    fun unseenFilterStopsMarkingSeenNotifWhenTransitionToAod() {
+        whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+        // GIVEN: Keyguard is not showing, shade is not expanded, and a notification is present
+        keyguardRepository.setKeyguardShowing(false)
+        whenever(statusBarStateController.isExpanded).thenReturn(false)
+        runKeyguardCoordinatorTest {
+            val fakeEntry = NotificationEntryBuilder().build()
+            collectionListener.onEntryAdded(fakeEntry)
+
+            // WHEN: The device transitions to AOD
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(to = KeyguardState.AOD, transitionState = TransitionState.STARTED),
+            )
+            testScheduler.runCurrent()
+
+            // WHEN: The shade is expanded
+            whenever(statusBarStateController.isExpanded).thenReturn(true)
+            statusBarStateListener.onExpandedChanged(true)
+            testScheduler.runCurrent()
+
+            // THEN: The notification is still treated as "unseen" and is not filtered out.
+            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+        }
+    }
+
+    @Test
     fun unseenFilter_headsUpMarkedAsSeen() {
         whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
 
@@ -373,6 +405,7 @@
                 headsUpManager,
                 keyguardNotifVisibilityProvider,
                 keyguardRepository,
+                keyguardTransitionRepository,
                 notifPipelineFlags,
                 testScope.backgroundScope,
                 sectionHeaderVisibilityProvider,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 7d02219..9186c35 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -100,7 +100,6 @@
         mNotificationTestHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL);
 
         FakeFeatureFlags fakeFeatureFlags = new FakeFeatureFlags();
-        fakeFeatureFlags.set(Flags.NOTIFICATION_ANIMATE_BIG_PICTURE, true);
         fakeFeatureFlags.set(Flags.SENSITIVE_REVEAL_ANIM, false);
         mNotificationTestHelper.setFeatureFlags(fakeFeatureFlags);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
new file mode 100644
index 0000000..2cc375b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.shelf.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class NotificationShelfInteractorTest : SysuiTestCase() {
+
+    private val keyguardRepository = FakeKeyguardRepository()
+    private val deviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository()
+    private val underTest =
+        NotificationShelfInteractor(keyguardRepository, deviceEntryFaceAuthRepository)
+
+    @Test
+    fun shelfIsNotStatic_whenKeyguardNotShowing() = runTest {
+        val shelfStatic by collectLastValue(underTest.isShelfStatic)
+
+        keyguardRepository.setKeyguardShowing(false)
+
+        assertThat(shelfStatic).isFalse()
+    }
+
+    @Test
+    fun shelfIsNotStatic_whenKeyguardShowingAndNotBypass() = runTest {
+        val shelfStatic by collectLastValue(underTest.isShelfStatic)
+
+        keyguardRepository.setKeyguardShowing(true)
+        deviceEntryFaceAuthRepository.isBypassEnabled.value = false
+
+        assertThat(shelfStatic).isFalse()
+    }
+
+    @Test
+    fun shelfIsStatic_whenBypass() = runTest {
+        val shelfStatic by collectLastValue(underTest.isShelfStatic)
+
+        keyguardRepository.setKeyguardShowing(true)
+        deviceEntryFaceAuthRepository.isBypassEnabled.value = true
+
+        assertThat(shelfStatic).isTrue()
+    }
+
+    @Test
+    fun shelfOnKeyguard_whenKeyguardShowing() = runTest {
+        val onKeyguard by collectLastValue(underTest.isShowingOnKeyguard)
+
+        keyguardRepository.setKeyguardShowing(true)
+
+        assertThat(onKeyguard).isTrue()
+    }
+
+    @Test
+    fun shelfNotOnKeyguard_whenKeyguardNotShowing() = runTest {
+        val onKeyguard by collectLastValue(underTest.isShowingOnKeyguard)
+
+        keyguardRepository.setKeyguardShowing(false)
+
+        assertThat(onKeyguard).isFalse()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
new file mode 100644
index 0000000..439edaf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.shelf.ui.viewmodel
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class NotificationShelfViewModelTest : SysuiTestCase() {
+
+    private val keyguardRepository = FakeKeyguardRepository()
+    private val deviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository()
+    private val interactor =
+        NotificationShelfInteractor(keyguardRepository, deviceEntryFaceAuthRepository)
+    private val underTest = NotificationShelfViewModel(interactor)
+
+    @Test
+    fun canModifyColorOfNotifications_whenKeyguardNotShowing() = runTest {
+        val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
+
+        keyguardRepository.setKeyguardShowing(false)
+
+        assertThat(canModifyNotifColor).isTrue()
+    }
+
+    @Test
+    fun canModifyColorOfNotifications_whenKeyguardShowingAndNotBypass() = runTest {
+        val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
+
+        keyguardRepository.setKeyguardShowing(true)
+        deviceEntryFaceAuthRepository.isBypassEnabled.value = false
+
+        assertThat(canModifyNotifColor).isTrue()
+    }
+
+    @Test
+    fun cannotModifyColorOfNotifications_whenBypass() = runTest {
+        val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
+
+        keyguardRepository.setKeyguardShowing(true)
+        deviceEntryFaceAuthRepository.isBypassEnabled.value = true
+
+        assertThat(canModifyNotifColor).isFalse()
+    }
+
+    @Test
+    fun isClickable_whenKeyguardShowing() = runTest {
+        val isClickable by collectLastValue(underTest.isClickable)
+
+        keyguardRepository.setKeyguardShowing(true)
+
+        assertThat(isClickable).isTrue()
+    }
+
+    @Test
+    fun isNotClickable_whenKeyguardNotShowing() = runTest {
+        val isClickable by collectLastValue(underTest.isClickable)
+
+        keyguardRepository.setKeyguardShowing(false)
+
+        assertThat(isClickable).isFalse()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
index e6f10cd..5279740 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
@@ -23,6 +23,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.media.controls.pipeline.MediaDataManager
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -48,6 +49,7 @@
     @Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController
     @Mock
     private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController
+    @Mock private lateinit var mediaDataManager: MediaDataManager
     @Mock private lateinit var stackLayout: NotificationStackScrollLayout
 
     private val testableResources = mContext.orCreateTestableResources
@@ -67,7 +69,9 @@
             NotificationStackSizeCalculator(
                 statusBarStateController = sysuiStatusBarStateController,
                 lockscreenShadeTransitionController = lockscreenShadeTransitionController,
-                testableResources.resources)
+                mediaDataManager = mediaDataManager,
+                testableResources.resources
+            )
     }
 
     @Test
@@ -76,7 +80,11 @@
 
         val maxNotifications =
             computeMaxKeyguardNotifications(
-                rows, spaceForNotifications = 0f, spaceForShelf = 0f, shelfHeight = 0f)
+                rows,
+                spaceForNotifications = 0f,
+                spaceForShelf = 0f,
+                shelfHeight = 0f
+            )
 
         assertThat(maxNotifications).isEqualTo(0)
     }
@@ -91,7 +99,8 @@
                 rows,
                 spaceForNotifications = Float.MAX_VALUE,
                 spaceForShelf = Float.MAX_VALUE,
-                shelfHeight)
+                shelfHeight
+            )
 
         assertThat(maxNotifications).isEqualTo(numberOfRows)
     }
@@ -111,6 +120,28 @@
     }
 
     @Test
+    fun computeMaxKeyguardNotifications_onLockscreenSpaceForMinHeightButNotIntrinsicHeight_returnsOne() {
+        setGapHeight(0f)
+        // No divider height since we're testing one element where index = 0
+
+        whenever(sysuiStatusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+        whenever(lockscreenShadeTransitionController.fractionToShade).thenReturn(0f)
+
+        val row = createMockRow(10f, isSticky = true)
+        whenever(row.getMinHeight(any())).thenReturn(5)
+
+        val maxNotifications =
+            computeMaxKeyguardNotifications(
+                listOf(row),
+                /* spaceForNotifications= */ 5f,
+                /* spaceForShelf= */ 0f,
+                /* shelfHeight= */ 0f
+            )
+
+        assertThat(maxNotifications).isEqualTo(1)
+    }
+
+    @Test
     fun computeMaxKeyguardNotifications_spaceForTwo_returnsTwo() {
         setGapHeight(gapHeight)
         val shelfHeight = shelfHeight + dividerHeight
@@ -126,7 +157,11 @@
 
         val maxNotifications =
             computeMaxKeyguardNotifications(
-                rows, spaceForNotifications + 1, spaceForShelf, shelfHeight)
+                rows,
+                spaceForNotifications + 1,
+                spaceForShelf,
+                shelfHeight
+            )
 
         assertThat(maxNotifications).isEqualTo(2)
     }
@@ -136,24 +171,23 @@
         // Each row in separate section.
         setGapHeight(gapHeight)
 
-        val spaceForNotifications =
+        val notifSpace =
             listOf(
                     rowHeight,
                     dividerHeight + gapHeight + rowHeight,
                 )
                 .sum()
 
-        val spaceForShelf = dividerHeight + gapHeight + shelfHeight
-        val spaceUsed = spaceForNotifications + spaceForShelf
+        val shelfSpace = dividerHeight + gapHeight + shelfHeight
+        val spaceUsed = notifSpace + shelfSpace
         val rows =
             listOf(createMockRow(rowHeight), createMockRow(rowHeight), createMockRow(rowHeight))
 
         val maxNotifications =
-            computeMaxKeyguardNotifications(rows, spaceForNotifications, spaceForShelf, shelfHeight)
+            computeMaxKeyguardNotifications(rows, notifSpace, shelfSpace, shelfHeight)
         assertThat(maxNotifications).isEqualTo(2)
 
-        val height =
-            sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight)
+        val height = sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight)
         assertThat(height).isEqualTo(spaceUsed)
     }
 
@@ -170,11 +204,14 @@
         // test that we only use space required
         val maxNotifications =
             computeMaxKeyguardNotifications(
-                rows, spaceForNotifications + 1, spaceForShelf, shelfHeight)
+                rows,
+                spaceForNotifications + 1,
+                spaceForShelf,
+                shelfHeight
+            )
         assertThat(maxNotifications).isEqualTo(1)
 
-        val height =
-            sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight)
+        val height = sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight)
         assertThat(height).isEqualTo(spaceUsed)
     }
 
@@ -200,7 +237,65 @@
     }
 
     @Test
-    fun spaceNeeded_onLockscreen_usesMinHeight() {
+    fun getSpaceNeeded_onLockscreenEnoughSpaceStickyHun_intrinsicHeight() {
+        setGapHeight(0f)
+        // No divider height since we're testing one element where index = 0
+
+        val row = createMockRow(10f, isSticky = true)
+        whenever(row.getMinHeight(any())).thenReturn(5)
+
+        val space =
+            sizeCalculator.getSpaceNeeded(
+                row,
+                visibleIndex = 0,
+                previousView = null,
+                stack = stackLayout,
+                onLockscreen = true
+            )
+        assertThat(space.whenEnoughSpace).isEqualTo(10f)
+    }
+
+    @Test
+    fun getSpaceNeeded_onLockscreenEnoughSpaceNotStickyHun_minHeight() {
+        setGapHeight(0f)
+        // No divider height since we're testing one element where index = 0
+
+        val row = createMockRow(rowHeight)
+        whenever(row.heightWithoutLockscreenConstraints).thenReturn(10)
+        whenever(row.getMinHeight(any())).thenReturn(5)
+
+        val space =
+            sizeCalculator.getSpaceNeeded(
+                row,
+                visibleIndex = 0,
+                previousView = null,
+                stack = stackLayout,
+                onLockscreen = true
+            )
+        assertThat(space.whenEnoughSpace).isEqualTo(5)
+    }
+
+    @Test
+    fun getSpaceNeeded_onLockscreenSavingSpaceStickyHun_minHeight() {
+        setGapHeight(0f)
+        // No divider height since we're testing one element where index = 0
+
+        val expandableView = createMockRow(10f, isSticky = true)
+        whenever(expandableView.getMinHeight(any())).thenReturn(5)
+
+        val space =
+            sizeCalculator.getSpaceNeeded(
+                expandableView,
+                visibleIndex = 0,
+                previousView = null,
+                stack = stackLayout,
+                onLockscreen = true
+            )
+        assertThat(space.whenSavingSpace).isEqualTo(5)
+    }
+
+    @Test
+    fun getSpaceNeeded_onLockscreenSavingSpaceNotStickyHun_minHeight() {
         setGapHeight(0f)
         // No divider height since we're testing one element where index = 0
 
@@ -209,51 +304,34 @@
         whenever(expandableView.intrinsicHeight).thenReturn(10)
 
         val space =
-            sizeCalculator.spaceNeeded(
+            sizeCalculator.getSpaceNeeded(
                 expandableView,
                 visibleIndex = 0,
                 previousView = null,
                 stack = stackLayout,
-                onLockscreen = true)
-        assertThat(space).isEqualTo(5)
+                onLockscreen = true
+            )
+        assertThat(space.whenSavingSpace).isEqualTo(5)
     }
 
     @Test
-    fun spaceNeeded_fsiHunOnLockscreen_usesIntrinsicHeight() {
-        setGapHeight(0f)
-        // No divider height since we're testing one element where index = 0
-
-        val expandableView = createMockStickyRow(rowHeight)
-        whenever(expandableView.getMinHeight(any())).thenReturn(5)
-        whenever(expandableView.intrinsicHeight).thenReturn(10)
-
-        val space =
-                sizeCalculator.spaceNeeded(
-                        expandableView,
-                        visibleIndex = 0,
-                        previousView = null,
-                        stack = stackLayout,
-                        onLockscreen = true)
-        assertThat(space).isEqualTo(10)
-    }
-
-    @Test
-    fun spaceNeeded_notOnLockscreen_usesIntrinsicHeight() {
+    fun getSpaceNeeded_notOnLockscreen_intrinsicHeight() {
         setGapHeight(0f)
         // No divider height since we're testing one element where index = 0
 
         val expandableView = createMockRow(rowHeight)
-        whenever(expandableView.getMinHeight(any())).thenReturn(5)
-        whenever(expandableView.intrinsicHeight).thenReturn(10)
+        whenever(expandableView.getMinHeight(any())).thenReturn(1)
 
         val space =
-            sizeCalculator.spaceNeeded(
+            sizeCalculator.getSpaceNeeded(
                 expandableView,
                 visibleIndex = 0,
                 previousView = null,
                 stack = stackLayout,
-                onLockscreen = false)
-        assertThat(space).isEqualTo(10)
+                onLockscreen = false
+            )
+        assertThat(space.whenEnoughSpace).isEqualTo(rowHeight)
+        assertThat(space.whenSavingSpace).isEqualTo(rowHeight)
     }
 
     private fun computeMaxKeyguardNotifications(
@@ -264,7 +342,11 @@
     ): Int {
         setupChildren(rows)
         return sizeCalculator.computeMaxKeyguardNotifications(
-            stackLayout, spaceForNotifications, spaceForShelf, shelfHeight)
+            stackLayout,
+            spaceForNotifications,
+            spaceForShelf,
+            shelfHeight
+        )
     }
 
     private fun setupChildren(children: List<ExpandableView>) {
@@ -280,11 +362,13 @@
 
     private fun createMockRow(
         height: Float = rowHeight,
+        isSticky: Boolean = false,
         isRemoved: Boolean = false,
-        visibility: Int = VISIBLE
+        visibility: Int = VISIBLE,
     ): ExpandableNotificationRow {
         val row = mock(ExpandableNotificationRow::class.java)
         val entry = mock(NotificationEntry::class.java)
+        whenever(entry.isStickyAndNotDemoted).thenReturn(isSticky)
         val sbn = mock(StatusBarNotification::class.java)
         whenever(entry.sbn).thenReturn(sbn)
         whenever(row.entry).thenReturn(entry)
@@ -292,25 +376,7 @@
         whenever(row.visibility).thenReturn(visibility)
         whenever(row.getMinHeight(any())).thenReturn(height.toInt())
         whenever(row.intrinsicHeight).thenReturn(height.toInt())
-        return row
-    }
-
-    private fun createMockStickyRow(
-            height: Float = rowHeight,
-            isRemoved: Boolean = false,
-            visibility: Int = VISIBLE
-    ): ExpandableNotificationRow {
-        val row = mock(ExpandableNotificationRow::class.java)
-        val entry = mock(NotificationEntry::class.java)
-        whenever(entry.isStickyAndNotDemoted).thenReturn(true)
-
-        val sbn = mock(StatusBarNotification::class.java)
-        whenever(entry.sbn).thenReturn(sbn)
-        whenever(row.entry).thenReturn(entry)
-        whenever(row.isRemoved).thenReturn(isRemoved)
-        whenever(row.visibility).thenReturn(visibility)
-        whenever(row.getMinHeight(any())).thenReturn(height.toInt())
-        whenever(row.intrinsicHeight).thenReturn(height.toInt())
+        whenever(row.heightWithoutLockscreenConstraints).thenReturn(height.toInt())
         return row
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index 746c92e..02c459b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -16,12 +16,10 @@
 
 package com.android.systemui.statusbar.phone
 
-import android.animation.Animator
 import android.os.Handler
 import android.os.PowerManager
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
-import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.SysuiTestCase
@@ -39,10 +37,8 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.Mock
-import org.mockito.Mockito
 import org.mockito.Mockito.anyLong
 import org.mockito.Mockito.never
-import org.mockito.Mockito.spy
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
@@ -111,27 +107,6 @@
         controller.onStartedWakingUp()
     }
 
-    @Test
-    fun testAnimClearsEndListener() {
-        val keyguardView = View(context)
-        val animator = spy(keyguardView.animate())
-        val keyguardSpy = spy(keyguardView)
-        Mockito.`when`(keyguardSpy.animate()).thenReturn(animator)
-        val listener = ArgumentCaptor.forClass(Animator.AnimatorListener::class.java)
-        val endAction = ArgumentCaptor.forClass(Runnable::class.java)
-        controller.animateInKeyguard(keyguardSpy, Runnable {})
-        Mockito.verify(animator).setListener(listener.capture())
-        Mockito.verify(animator).withEndAction(endAction.capture())
-
-        // Verify that the listener is cleared if we cancel it.
-        listener.value.onAnimationCancel(null)
-        Mockito.verify(animator).setListener(null)
-
-        // Verify that the listener is also cleared if the end action is triggered.
-        endAction.value.run()
-        verify(animator, times(2)).setListener(null)
-    }
-
     /**
      * The AOD UI is shown during the screen off animation, after a delay to allow the light reveal
      * animation to start. If the device is woken up during the screen off, we should *never* do
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
index 3ced7b2..b2bbcfd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
@@ -49,6 +49,8 @@
             FIVE_G_OVERRIDE_KEY to TelephonyIcons.NR_5G,
         )
 
+    private val interactorCache: MutableMap<Int, FakeMobileIconInteractor> = mutableMapOf()
+
     override val isDefaultConnectionFailed = MutableStateFlow(false)
 
     override val filteredSubscriptions = MutableStateFlow<List<SubscriptionModel>>(listOf())
@@ -75,7 +77,15 @@
 
     /** Always returns a new fake interactor */
     override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor {
-        return FakeMobileIconInteractor(tableLogBuffer)
+        return FakeMobileIconInteractor(tableLogBuffer).also { interactorCache[subId] = it }
+    }
+
+    /**
+     * Returns the most recently created interactor for the given subId, or null if an interactor
+     * has never been created for that sub.
+     */
+    fun getInteractorForSubId(subId: Int): FakeMobileIconInteractor? {
+        return interactorCache[subId]
     }
 
     companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
index ddb7f4d..f8e1aa9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
 
 import androidx.test.filters.SmallTest
+import com.android.settingslib.mobile.TelephonyIcons
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.phone.StatusBarLocation
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
@@ -24,6 +25,7 @@
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel
 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
 import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
@@ -32,9 +34,8 @@
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
@@ -69,14 +70,8 @@
                 FakeConnectivityRepository(),
             )
 
-        val subscriptionIdsFlow =
-            interactor.filteredSubscriptions
-                .map { subs -> subs.map { it.subscriptionId } }
-                .stateIn(testScope.backgroundScope, SharingStarted.WhileSubscribed(), listOf())
-
         underTest =
             MobileIconsViewModel(
-                subscriptionIdsFlow,
                 logger,
                 verboseLogger,
                 interactor,
@@ -90,6 +85,32 @@
     }
 
     @Test
+    fun subscriptionIdsFlow_matchesInteractor() =
+        testScope.runTest {
+            var latest: List<Int>? = null
+            val job = underTest.subscriptionIdsFlow.onEach { latest = it }.launchIn(this)
+
+            interactor.filteredSubscriptions.value =
+                listOf(
+                    SubscriptionModel(subscriptionId = 1, isOpportunistic = false),
+                )
+            assertThat(latest).isEqualTo(listOf(1))
+
+            interactor.filteredSubscriptions.value =
+                listOf(
+                    SubscriptionModel(subscriptionId = 2, isOpportunistic = false),
+                    SubscriptionModel(subscriptionId = 5, isOpportunistic = true),
+                    SubscriptionModel(subscriptionId = 7, isOpportunistic = true),
+                )
+            assertThat(latest).isEqualTo(listOf(2, 5, 7))
+
+            interactor.filteredSubscriptions.value = emptyList()
+            assertThat(latest).isEmpty()
+
+            job.cancel()
+        }
+
+    @Test
     fun `caching - mobile icon view model is reused for same sub id`() =
         testScope.runTest {
             val model1 = underTest.viewModelForSub(1, StatusBarLocation.HOME)
@@ -116,8 +137,179 @@
             assertThat(underTest.mobileIconSubIdCache).containsExactly(2, model2.commonImpl)
         }
 
+    @Test
+    fun firstMobileSubShowingNetworkTypeIcon_noSubs_false() =
+        testScope.runTest {
+            var latest: Boolean? = null
+            val job =
+                underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this)
+
+            interactor.filteredSubscriptions.value = emptyList()
+
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun firstMobileSubShowingNetworkTypeIcon_oneSub_notShowingRat_false() =
+        testScope.runTest {
+            var latest: Boolean? = null
+            val job =
+                underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this)
+
+            interactor.filteredSubscriptions.value = listOf(SUB_1)
+            // The unknown icon group doesn't show a RAT
+            interactor.getInteractorForSubId(1)!!.networkTypeIconGroup.value =
+                NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN)
+
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun firstMobileSubShowingNetworkTypeIcon_oneSub_showingRat_true() =
+        testScope.runTest {
+            var latest: Boolean? = null
+            val job =
+                underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this)
+
+            interactor.filteredSubscriptions.value = listOf(SUB_1)
+            // The 3G icon group will show a RAT
+            interactor.getInteractorForSubId(1)!!.networkTypeIconGroup.value =
+                NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
+
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun firstMobileSubShowingNetworkTypeIcon_updatesAsSubUpdates() =
+        testScope.runTest {
+            var latest: Boolean? = null
+            val job =
+                underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this)
+
+            interactor.filteredSubscriptions.value = listOf(SUB_1)
+            val sub1Interactor = interactor.getInteractorForSubId(1)!!
+
+            sub1Interactor.networkTypeIconGroup.value =
+                NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
+            assertThat(latest).isTrue()
+
+            sub1Interactor.networkTypeIconGroup.value =
+                NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN)
+            assertThat(latest).isFalse()
+
+            sub1Interactor.networkTypeIconGroup.value =
+                NetworkTypeIconModel.DefaultIcon(TelephonyIcons.LTE)
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun firstMobileSubShowingNetworkTypeIcon_multipleSubs_lastSubNotShowingRat_false() =
+        testScope.runTest {
+            var latest: Boolean? = null
+            val job =
+                underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this)
+
+            interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+            interactor.getInteractorForSubId(1)?.networkTypeIconGroup?.value =
+                NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
+            interactor.getInteractorForSubId(2)!!.networkTypeIconGroup.value =
+                NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN)
+
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun firstMobileSubShowingNetworkTypeIcon_multipleSubs_lastSubShowingRat_true() =
+        testScope.runTest {
+            var latest: Boolean? = null
+            val job =
+                underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this)
+
+            interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+            interactor.getInteractorForSubId(1)?.networkTypeIconGroup?.value =
+                NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN)
+            interactor.getInteractorForSubId(2)!!.networkTypeIconGroup.value =
+                NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
+
+            assertThat(latest).isTrue()
+            job.cancel()
+        }
+
+    @Test
+    fun firstMobileSubShowingNetworkTypeIcon_subListUpdates_valAlsoUpdates() =
+        testScope.runTest {
+            var latest: Boolean? = null
+            val job =
+                underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this)
+
+            interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+            interactor.getInteractorForSubId(1)?.networkTypeIconGroup?.value =
+                NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN)
+            interactor.getInteractorForSubId(2)!!.networkTypeIconGroup.value =
+                NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
+
+            assertThat(latest).isTrue()
+
+            // WHEN the sub list gets new subscriptions where the last subscription is not showing
+            // the network type icon
+            interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2, SUB_3)
+            interactor.getInteractorForSubId(3)!!.networkTypeIconGroup.value =
+                NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN)
+
+            // THEN the flow updates
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun firstMobileSubShowingNetworkTypeIcon_subListReorders_valAlsoUpdates() =
+        testScope.runTest {
+            var latest: Boolean? = null
+            val job =
+                underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this)
+
+            interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+            // Immediately switch the order so that we've created both interactors
+            interactor.filteredSubscriptions.value = listOf(SUB_2, SUB_1)
+            val sub1Interactor = interactor.getInteractorForSubId(1)!!
+            val sub2Interactor = interactor.getInteractorForSubId(2)!!
+
+            interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+            sub1Interactor.networkTypeIconGroup.value =
+                NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN)
+            sub2Interactor.networkTypeIconGroup.value =
+                NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
+            assertThat(latest).isTrue()
+
+            // WHEN sub1 becomes last and sub1 has no network type icon
+            interactor.filteredSubscriptions.value = listOf(SUB_2, SUB_1)
+
+            // THEN the flow updates
+            assertThat(latest).isFalse()
+
+            // WHEN sub2 becomes last and sub2 has a network type icon
+            interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+
+            // THEN the flow updates
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
     companion object {
         private val SUB_1 = SubscriptionModel(subscriptionId = 1, isOpportunistic = false)
         private val SUB_2 = SubscriptionModel(subscriptionId = 2, isOpportunistic = false)
+        private val SUB_3 = SubscriptionModel(subscriptionId = 3, isOpportunistic = false)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index 5c19108..0d51af2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -50,6 +50,7 @@
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -98,12 +99,12 @@
         val viewModelCommon =
             WifiViewModel(
                 airplaneModeViewModel,
+                shouldShowSignalSpacerProvider = { MutableStateFlow(false) },
                 connectivityConstants,
                 context,
                 tableLogBuffer,
                 interactor,
                 scope,
-                statusBarPipelineFlags,
                 wifiConstants,
             )
         viewModel =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
index ffe990b..e6724d8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
@@ -27,7 +27,6 @@
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
-import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
@@ -46,6 +45,7 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.yield
@@ -66,7 +66,6 @@
 
     private lateinit var underTest: WifiViewModel
 
-    @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
     @Mock private lateinit var tableLogBuffer: TableLogBuffer
     @Mock private lateinit var connectivityConstants: ConnectivityConstants
     @Mock private lateinit var wifiConstants: WifiConstants
@@ -121,12 +120,12 @@
             underTest =
                 WifiViewModel(
                     airplaneModeViewModel,
+                    shouldShowSignalSpacerProvider = { MutableStateFlow(false) },
                     connectivityConstants,
                     context,
                     tableLogBuffer,
                     interactor,
                     scope,
-                    statusBarPipelineFlags,
                     wifiConstants,
                 )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index 802e360..0e303b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -39,8 +39,8 @@
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.runBlocking
@@ -53,7 +53,6 @@
 import org.mockito.MockitoAnnotations
 
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 class WifiViewModelTest : SysuiTestCase() {
 
@@ -68,6 +67,7 @@
     private lateinit var wifiRepository: FakeWifiRepository
     private lateinit var interactor: WifiInteractor
     private lateinit var airplaneModeViewModel: AirplaneModeViewModel
+    private val shouldShowSignalSpacerProviderFlow = MutableStateFlow(false)
     private lateinit var scope: CoroutineScope
 
     @Before
@@ -473,6 +473,34 @@
             job.cancel()
         }
 
+    @Test
+    fun signalSpacer_firstSubNotShowingNetworkTypeIcon_outputsFalse() =
+        runBlocking(IMMEDIATE) {
+            var latest: Boolean? = null
+            val job = underTest.isSignalSpacerVisible.onEach { latest = it }.launchIn(this)
+
+            shouldShowSignalSpacerProviderFlow.value = false
+            yield()
+
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun signalSpacer_firstSubIsShowingNetworkTypeIcon_outputsTrue() =
+        runBlocking(IMMEDIATE) {
+            var latest: Boolean? = null
+            val job = underTest.isSignalSpacerVisible.onEach { latest = it }.launchIn(this)
+
+            shouldShowSignalSpacerProviderFlow.value = true
+            yield()
+
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
     private fun createAndSetViewModel() {
         // [WifiViewModel] creates its flows as soon as it's instantiated, and some of those flow
         // creations rely on certain config values that we mock out in individual tests. This method
@@ -480,12 +508,12 @@
         underTest =
             WifiViewModel(
                 airplaneModeViewModel,
+                { shouldShowSignalSpacerProviderFlow },
                 connectivityConstants,
                 context,
                 tableLogBuffer,
                 interactor,
                 scope,
-                statusBarPipelineFlags,
                 wifiConstants,
             )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index 1eee08c..91c88ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -21,6 +21,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS;
 
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
@@ -167,8 +168,10 @@
         mBatteryController.setPowerSaveMode(false, mView);
 
         StaticInOrder inOrder = inOrder(staticMockMarker(BatterySaverUtils.class));
-        inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), true, true));
-        inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), false, true));
+        inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), true, true,
+                SAVER_ENABLED_QS));
+        inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), false, true,
+                SAVER_ENABLED_QS));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 01e94ba..391c8ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -62,7 +62,7 @@
 import android.window.WindowOnBackInvokedDispatcher;
 
 import androidx.annotation.NonNull;
-import androidx.core.animation.AnimatorTestRule2;
+import androidx.core.animation.AnimatorTestRule;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.UiEventLogger;
@@ -110,7 +110,7 @@
     private final UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
 
     @ClassRule
-    public static AnimatorTestRule2 mAnimatorTestRule = new AnimatorTestRule2();
+    public static AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
 
     @Before
     public void setUp() throws Exception {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
new file mode 100644
index 0000000..9cf3e44
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.volume;
+
+import static android.media.AudioManager.CSD_WARNING_DOSE_REACHED_1X;
+import static android.media.AudioManager.CSD_WARNING_DOSE_REPEATED_5X;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.media.AudioManager;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.messages.nano.SystemMessageProto;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class CsdWarningDialogTest extends SysuiTestCase {
+
+    private NotificationManager mNotificationManager;
+    private AudioManager mAudioManager;
+
+    @Before
+    public void setup() {
+        mNotificationManager = mock(NotificationManager.class);
+        getContext().addMockSystemService(NotificationManager.class, mNotificationManager);
+
+        mAudioManager = mock(AudioManager.class);
+        getContext().addMockSystemService(AudioManager.class, mAudioManager);
+    }
+
+    @Test
+    public void create1XCsdDialogAndWait_sendsNotification() {
+        FakeExecutor executor =  new FakeExecutor(new FakeSystemClock());
+        // instantiate directly instead of via factory; we don't want executor to be @Background
+        CsdWarningDialog dialog = new CsdWarningDialog(CSD_WARNING_DOSE_REACHED_1X, mContext,
+                mAudioManager, mNotificationManager, executor, null);
+
+        dialog.show();
+        executor.advanceClockToLast();
+        executor.runAllReady();
+        dialog.dismiss();
+
+        verify(mNotificationManager).notify(
+                eq(SystemMessageProto.SystemMessage.NOTE_CSD_LOWER_AUDIO), any(Notification.class));
+    }
+
+    @Test
+    public void create5XCsdDiSalogAndWait_willNotSendNotification() {
+        FakeExecutor executor =  new FakeExecutor(new FakeSystemClock());
+        CsdWarningDialog dialog = new CsdWarningDialog(CSD_WARNING_DOSE_REPEATED_5X, mContext,
+                mAudioManager, mNotificationManager, executor, null);
+
+        dialog.show();
+        executor.advanceClockToLast();
+        executor.runAllReady();
+        dialog.dismiss();
+
+        verify(mNotificationManager, never()).notify(
+                eq(SystemMessageProto.SystemMessage.NOTE_CSD_LOWER_AUDIO), any(Notification.class));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index d419095..eb26888 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -102,6 +102,15 @@
     InteractionJankMonitor mInteractionJankMonitor;
     @Mock
     private DumpManager mDumpManager;
+    @Mock CsdWarningDialog mCsdWarningDialog;
+
+    private final CsdWarningDialog.Factory mCsdWarningDialogFactory =
+            new CsdWarningDialog.Factory() {
+        @Override
+        public CsdWarningDialog create(int warningType, Runnable onCleanup) {
+            return mCsdWarningDialog;
+        }
+    };
 
     @Before
     public void setup() throws Exception {
@@ -124,6 +133,7 @@
                 mInteractionJankMonitor,
                 mDeviceConfigProxy,
                 mExecutor,
+                mCsdWarningDialogFactory,
                 mDumpManager
             );
         mDialog.init(0, null);
@@ -352,6 +362,14 @@
         mDialog.getDialogView().animate().cancel();
     }
 
+    @Test
+    public void showCsdWarning_dialogShown() {
+        mDialog.showCsdWarningH(AudioManager.CSD_WARNING_DOSE_REACHED_1X,
+                CsdWarningDialog.NO_ACTION_TIMEOUT_MS);
+
+        verify(mCsdWarningDialog).show();
+    }
+
 /*
     @Test
     public void testContentDescriptions() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
new file mode 100644
index 0000000..738f09d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.keyguard.FaceAuthUiEvent
+import com.android.systemui.keyguard.shared.model.AuthenticationStatus
+import com.android.systemui.keyguard.shared.model.DetectionStatus
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
+
+class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository {
+
+    override val isAuthenticated = MutableStateFlow(false)
+    override val canRunFaceAuth = MutableStateFlow(false)
+    private val _authenticationStatus = MutableStateFlow<AuthenticationStatus?>(null)
+    override val authenticationStatus: Flow<AuthenticationStatus> =
+        _authenticationStatus.filterNotNull()
+    fun setAuthenticationStatus(status: AuthenticationStatus) {
+        _authenticationStatus.value = status
+    }
+    private val _detectionStatus = MutableStateFlow<DetectionStatus?>(null)
+    override val detectionStatus: Flow<DetectionStatus>
+        get() = _detectionStatus.filterNotNull()
+    fun setDetectionStatus(status: DetectionStatus) {
+        _detectionStatus.value = status
+    }
+    override val isLockedOut = MutableStateFlow(false)
+    private val _runningAuthRequest = MutableStateFlow<Pair<FaceAuthUiEvent, Boolean>?>(null)
+    val runningAuthRequest: StateFlow<Pair<FaceAuthUiEvent, Boolean>?> =
+        _runningAuthRequest.asStateFlow()
+
+    private val _isAuthRunning = MutableStateFlow(false)
+    override val isAuthRunning: StateFlow<Boolean> = _isAuthRunning
+
+    override val isBypassEnabled = MutableStateFlow(false)
+
+    override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
+        _runningAuthRequest.value = uiEvent to fallbackToDetection
+        _isAuthRunning.value = true
+    }
+
+    override fun cancel() {
+        _isAuthRunning.value = false
+        _runningAuthRequest.value = null
+    }
+}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldRemoteFilter.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldRemoteFilter.kt
index 0b019d1..3041888 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldRemoteFilter.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldRemoteFilter.kt
@@ -35,7 +35,7 @@
 
     private var inProgress = false
 
-    private var processedProgress: Float = 0.0f
+    private var processedProgress: Float = 1.0f
         set(newProgress) {
             if (inProgress) {
                 logCounter({ "$TAG#filtered_progress" }, newProgress)
diff --git a/packages/overlays/Android.mk b/packages/overlays/Android.mk
index cbca3f0..a41d0e5 100644
--- a/packages/overlays/Android.mk
+++ b/packages/overlays/Android.mk
@@ -32,6 +32,7 @@
 	NavigationBarModeGesturalOverlayWideBack \
 	NavigationBarModeGesturalOverlayExtraWideBack \
 	TransparentNavigationBarOverlay \
+	NotesRoleEnabledOverlay \
 	preinstalled-packages-platform-overlays.xml
 
 include $(BUILD_PHONY_PACKAGE)
diff --git a/core/tests/expresslog/Android.bp b/packages/overlays/NotesRoleEnabledOverlay/Android.bp
similarity index 64%
rename from core/tests/expresslog/Android.bp
rename to packages/overlays/NotesRoleEnabledOverlay/Android.bp
index cab49a7..68ebd96 100644
--- a/core/tests/expresslog/Android.bp
+++ b/packages/overlays/NotesRoleEnabledOverlay/Android.bp
@@ -1,16 +1,18 @@
-// Copyright (C) 2023 The Android Open Source Project
+//
+//  Copyright 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
+//     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 {
     // See: http://go/android-license-faq
@@ -21,27 +23,8 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
-android_test {
-    name: "ExpressLogTests",
-
-    srcs: [
-        "src/**/*.java",
-    ],
-
-    static_libs: [
-        "androidx.test.rules",
-        "modules-utils-build",
-    ],
-
-    libs: [
-        "android.test.base",
-        "android.test.runner",
-    ],
-
-    platform_apis: true,
-    test_suites: [
-        "general-tests",
-    ],
-
-    certificate: "platform",
+runtime_resource_overlay {
+    name: "NotesRoleEnabledOverlay",
+    theme: "NotesRoleEnabled",
+    product_specific: true,
 }
diff --git a/packages/overlays/NotesRoleEnabledOverlay/AndroidManifest.xml b/packages/overlays/NotesRoleEnabledOverlay/AndroidManifest.xml
new file mode 100644
index 0000000..c01178d
--- /dev/null
+++ b/packages/overlays/NotesRoleEnabledOverlay/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<!--
+/**
+ * Copyright (c) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.role.notes.enabled"
+        android:versionCode="1"
+        android:versionName="1.0">
+    <overlay android:targetPackage="android"
+        android:priority="1"/>
+
+    <application android:label="@string/notes_role_enabled_overlay_title" android:hasCode="false"/>
+</manifest>
\ No newline at end of file
diff --git a/packages/overlays/NotesRoleEnabledOverlay/res/values/config.xml b/packages/overlays/NotesRoleEnabledOverlay/res/values/config.xml
new file mode 100644
index 0000000..f27f5f4
--- /dev/null
+++ b/packages/overlays/NotesRoleEnabledOverlay/res/values/config.xml
@@ -0,0 +1,28 @@
+<?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.
+ */
+-->
+<resources>
+    <!-- Whether the default notes role should be enabled. In builds without 
+    device-specific overlays, this can be controlled in developer options. -->
+     <bool name="config_enableDefaultNotes">true</bool>
+
+    <!-- Whether the default notes role for work profile should be enabled. 
+    In builds without device-specific overlays, this can be controlled in 
+    developer options. -->
+    <bool name="config_enableDefaultNotesForWorkProfile">true</bool>
+</resources>
diff --git a/packages/overlays/NotesRoleEnabledOverlay/res/values/strings.xml b/packages/overlays/NotesRoleEnabledOverlay/res/values/strings.xml
new file mode 100644
index 0000000..3edbb57
--- /dev/null
+++ b/packages/overlays/NotesRoleEnabledOverlay/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2019, 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Name of overlay [CHAR LIMIT=64] -->
+    <string name="notes_role_enabled_overlay_title" translatable="false">Notes Role enabled</string>
+</resources>
\ No newline at end of file
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 4702734..21d0979 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -402,5 +402,7 @@
     // Package: android
     NOTE_ALL_MANAGED_SUBSCRIPTIONS_AND_MANAGED_PROFILE_OFF = 1006;
 
+    // Notify the user that audio was lowered based on Calculated Sound Dose (CSD)
+    NOTE_CSD_LOWER_AUDIO = 1007;
   }
 }
diff --git a/services/Android.bp b/services/Android.bp
index 6e6c553..b0a0e5e 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -112,6 +112,7 @@
         ":services.searchui-sources",
         ":services.selectiontoolbar-sources",
         ":services.smartspace-sources",
+        ":services.soundtrigger-sources",
         ":services.systemcaptions-sources",
         ":services.translation-sources",
         ":services.texttospeech-sources",
@@ -169,6 +170,7 @@
         "services.searchui",
         "services.selectiontoolbar",
         "services.smartspace",
+        "services.soundtrigger",
         "services.systemcaptions",
         "services.translation",
         "services.texttospeech",
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 51325e7..0bdb0c8 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -112,6 +112,7 @@
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.view.Display;
 import android.view.IWindow;
 import android.view.InputDevice;
@@ -160,6 +161,7 @@
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.utils.Slogf;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 import com.android.settingslib.RestrictedLockUtils;
@@ -301,7 +303,23 @@
     private final List<SendWindowStateChangedEventRunnable> mSendWindowStateChangedEventRunnables =
             new ArrayList<>();
 
-    private int mCurrentUserId = UserHandle.USER_SYSTEM;
+    @GuardedBy("mLock")
+    private @UserIdInt int mCurrentUserId = UserHandle.USER_SYSTEM;
+
+    // TODO(b/255426725): temporary workaround to support visible background users for UiAutomation:
+    // when the UiAutomation is set in a visible background user, mCurrentUserId points to that user
+    // and mRealCurrentUserId points to the "real" current user; otherwise, mRealCurrentUserId
+    // is set as UserHandle.USER_CURRENT.
+    @GuardedBy("mLock")
+    private @UserIdInt int mRealCurrentUserId = UserHandle.USER_CURRENT;
+
+    // TODO(b/255426725): temporary workaround to support visible background users for UiAutomation
+    // purposes - in the long term, the whole service should be refactored so it handles "visible"
+    // users, not current user. Notice that because this is temporary, it's not trying to optimize
+    // performance / utilization (for example, it's not using an IntArray)
+    @GuardedBy("mLock")
+    @Nullable // only set when device supports visible background users
+    private final SparseBooleanArray mVisibleBgUserIds;
 
     //TODO: Remove this hack
     private boolean mInitialized;
@@ -316,6 +334,7 @@
     private SparseArray<SurfaceControl> mA11yOverlayLayers = new SparseArray<>();
 
     private final FlashNotificationsController mFlashNotificationsController;
+    private final UserManagerInternal mUmi;
 
     private AccessibilityUserState getCurrentUserStateLocked() {
         return getUserStateLocked(mCurrentUserId);
@@ -445,6 +464,10 @@
             mHasInputFilter = true;
         }
         mFlashNotificationsController = new FlashNotificationsController(mContext);
+        mUmi = LocalServices.getService(UserManagerInternal.class);
+        // TODO(b/255426725): not used on tests
+        mVisibleBgUserIds = null;
+
         init();
     }
 
@@ -477,6 +500,15 @@
         mProxyManager = new ProxyManager(mLock, mA11yWindowManager, mContext, mMainHandler,
                 mUiAutomationManager, this);
         mFlashNotificationsController = new FlashNotificationsController(mContext);
+        mUmi = LocalServices.getService(UserManagerInternal.class);
+
+        if (UserManager.isVisibleBackgroundUsersEnabled()) {
+            mVisibleBgUserIds = new SparseBooleanArray();
+            mUmi.addUserVisibilityListener((u, v) -> onUserVisibilityChanged(u, v));
+        } else {
+            mVisibleBgUserIds = null;
+        }
+
         init();
     }
 
@@ -493,6 +525,12 @@
         return mCurrentUserId;
     }
 
+    @GuardedBy("mLock")
+    @Override
+    public SparseBooleanArray getVisibleUserIdsLocked() {
+        return mVisibleBgUserIds;
+    }
+
     @Override
     public boolean isAccessibilityButtonShown() {
         return mIsAccessibilityButtonShown;
@@ -1362,6 +1400,7 @@
     public void registerUiTestAutomationService(IBinder owner,
             IAccessibilityServiceClient serviceClient,
             AccessibilityServiceInfo accessibilityServiceInfo,
+            int userId,
             int flags) {
         if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
             mTraceManager.logTrace(LOG_TAG + ".registerUiTestAutomationService",
@@ -1374,6 +1413,7 @@
                 FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE);
 
         synchronized (mLock) {
+            changeCurrentUserForTestAutomationIfNeededLocked(userId);
             mUiAutomationManager.registerUiTestAutomationServiceLocked(owner, serviceClient,
                     mContext, accessibilityServiceInfo, sIdCounter++, mMainHandler,
                     mSecurityPolicy, this, getTraceManager(), mWindowManagerService,
@@ -1390,9 +1430,49 @@
         }
         synchronized (mLock) {
             mUiAutomationManager.unregisterUiTestAutomationServiceLocked(serviceClient);
+            restoreCurrentUserAfterTestAutomationIfNeededLocked();
         }
     }
 
+    // TODO(b/255426725): temporary workaround to support visible background users for UiAutomation
+    @GuardedBy("mLock")
+    private void changeCurrentUserForTestAutomationIfNeededLocked(@UserIdInt int userId) {
+        if (mVisibleBgUserIds == null) {
+            Slogf.d(LOG_TAG, "changeCurrentUserForTestAutomationIfNeededLocked(%d): ignoring "
+                    + "because device doesn't support visible background users", userId);
+            return;
+        }
+        if (!mVisibleBgUserIds.get(userId)) {
+            Slogf.wtf(LOG_TAG, "Cannot change current user to %d as it's not visible "
+                    + "(mVisibleUsers=%s)", userId, mVisibleBgUserIds);
+            return;
+        }
+        if (mCurrentUserId == userId) {
+            Slogf.w(LOG_TAG, "NOT changing current user for test automation purposes as it is "
+                    + "already %d", mCurrentUserId);
+            return;
+        }
+        Slogf.i(LOG_TAG, "Changing current user from %d to %d for test automation purposes",
+                mCurrentUserId, userId);
+        mRealCurrentUserId = mCurrentUserId;
+        switchUser(userId);
+    }
+
+    // TODO(b/255426725): temporary workaround to support visible background users for UiAutomation
+    @GuardedBy("mLock")
+    private void restoreCurrentUserAfterTestAutomationIfNeededLocked() {
+        if (mVisibleBgUserIds == null) {
+            Slogf.d(LOG_TAG, "restoreCurrentUserForTestAutomationIfNeededLocked(): ignoring "
+                    + "because device doesn't support visible background users");
+            return;
+        }
+        Slogf.i(LOG_TAG, "Restoring current user to %d after using %d for test automation purposes",
+                mRealCurrentUserId, mCurrentUserId);
+        int currentUserId = mRealCurrentUserId;
+        mRealCurrentUserId = UserHandle.USER_CURRENT;
+        switchUser(currentUserId);
+    }
+
     @Override
     public IBinder getWindowToken(int windowId, int userId) {
         if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
@@ -2291,8 +2371,7 @@
     private void updateServicesLocked(AccessibilityUserState userState) {
         Map<ComponentName, AccessibilityServiceConnection> componentNameToServiceMap =
                 userState.mComponentNameToServiceMap;
-        boolean isUnlockingOrUnlocked = LocalServices.getService(UserManagerInternal.class)
-                    .isUserUnlockingOrUnlocked(userState.mUserId);
+        boolean isUnlockingOrUnlocked = mUmi.isUserUnlockingOrUnlocked(userState.mUserId);
 
         for (int i = 0, count = userState.mInstalledServices.size(); i < count; i++) {
             AccessibilityServiceInfo installedService = userState.mInstalledServices.get(i);
@@ -2593,6 +2672,19 @@
         }
     }
 
+    private void onUserVisibilityChanged(@UserIdInt int userId, boolean visible) {
+        if (DEBUG) {
+            Slogf.d(LOG_TAG, "onUserVisibilityChanged(): %d => %b", userId, visible);
+        }
+        synchronized (mLock) {
+            if (visible) {
+                mVisibleBgUserIds.put(userId, visible);
+            } else {
+                mVisibleBgUserIds.delete(userId);
+            }
+        }
+    }
+
     /**
      * Called when any property of the user state has changed.
      *
@@ -4025,7 +4117,16 @@
             pw.println("ACCESSIBILITY MANAGER (dumpsys accessibility)");
             pw.println();
             pw.append("currentUserId=").append(String.valueOf(mCurrentUserId));
+            if (mRealCurrentUserId != UserHandle.USER_CURRENT
+                    && mCurrentUserId != mRealCurrentUserId) {
+                pw.append(" (set for UiAutomation purposes; \"real\" current user is ")
+                        .append(String.valueOf(mRealCurrentUserId)).append(")");
+            }
             pw.println();
+            if (mVisibleBgUserIds != null) {
+                pw.append("visibleBgUserIds=").append(mVisibleBgUserIds.toString());
+                pw.println();
+            }
             pw.append("hasWindowMagnificationConnection=").append(
                     String.valueOf(getWindowMagnificationMgr().isConnected()));
             pw.println();
@@ -4052,6 +4153,7 @@
             }
             pw.println();
             mProxyManager.dump(fd, pw, args);
+            mA11yDisplayListener.dump(fd, pw, args);
         }
     }
 
@@ -4437,6 +4539,20 @@
             /* do nothing */
         }
 
+        void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            pw.println("Accessibility Display Listener:");
+            pw.println("    SystemUI uid: " + mSystemUiUid);
+            int size = mDisplaysList.size();
+            pw.printf("    %d valid display%s: ", size, (size == 1 ? "" : "s"));
+            for (int i = 0; i < size; i++) {
+                pw.print(mDisplaysList.get(i).getDisplayId());
+                if (i < size - 1) {
+                    pw.print(", ");
+                }
+            }
+            pw.println();
+        }
+
         private boolean isValidDisplay(@Nullable Display display) {
             if (display == null || display.getType() == Display.TYPE_OVERLAY) {
                 return false;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
index c37ea50..8865623 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
@@ -39,6 +39,7 @@
 import android.os.UserManager;
 import android.util.ArraySet;
 import android.util.Slog;
+import android.util.SparseBooleanArray;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.inputmethod.InputMethodInfo;
 
@@ -88,6 +89,12 @@
          */
         int getCurrentUserIdLocked();
         // TODO: Should include resolveProfileParentLocked, but that was already in SecurityPolicy
+
+        // TODO(b/255426725): temporary hack; see comment on A11YMS.mVisibleBgUserIds
+        /**
+         * Returns the {@link android.os.UserManager#getVisibleUsers() visible users}.
+         */
+        @Nullable SparseBooleanArray getVisibleUserIdsLocked();
     }
 
     private final Context mContext;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index baed181..a8a5365 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -51,6 +51,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.accessibility.AccessibilitySecurityPolicy.AccessibilityUserManager;
+import com.android.server.utils.Slogf;
 import com.android.server.wm.WindowManagerInternal;
 
 import java.io.FileDescriptor;
@@ -59,6 +60,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * This class provides APIs for accessibility manager to manage {@link AccessibilityWindowInfo}s and
@@ -67,6 +69,7 @@
 public class AccessibilityWindowManager {
     private static final String LOG_TAG = "AccessibilityWindowManager";
     private static final boolean DEBUG = false;
+    private static final boolean VERBOSE = false;
 
     private static int sNextWindowId;
 
@@ -209,6 +212,9 @@
          * Constructor for DisplayWindowsObserver.
          */
         DisplayWindowsObserver(int displayId) {
+            if (DEBUG) {
+                Slogf.d(LOG_TAG, "Creating DisplayWindowsObserver for displayId %d", displayId);
+            }
             mDisplayId = displayId;
         }
 
@@ -430,12 +436,27 @@
             synchronized (mLock) {
                 updateWindowsByWindowAttributesLocked(windows);
                 if (DEBUG) {
-                    Slog.i(LOG_TAG, "Display Id = " + mDisplayId);
-                    Slog.i(LOG_TAG, "Windows changed: " + windows);
+                    Slogf.i(LOG_TAG, "mDisplayId=%d, topFocusedDisplayId=%d, currentUserId=%d, "
+                            + "visibleBgUsers=%s", mDisplayId, topFocusedDisplayId,
+                            mAccessibilityUserManager.getCurrentUserIdLocked(),
+                            mAccessibilityUserManager.getVisibleUserIdsLocked());
+                    if (VERBOSE) {
+                        Slogf.i(LOG_TAG, "%d windows changed: %s ", windows.size(), windows);
+                    } else {
+                        List<String> windowsInfo = windows.stream()
+                                .map(w -> "{displayId=" + w.displayId + ", title=" + w.title + "}")
+                                .collect(Collectors.toList());
+                        Slogf.i(LOG_TAG, "%d windows changed: %s", windows.size(), windowsInfo);
+                    }
                 }
                 if (shouldUpdateWindowsLocked(forceSend, windows)) {
                     mTopFocusedDisplayId = topFocusedDisplayId;
                     mTopFocusedWindowToken = topFocusedWindowToken;
+                    if (DEBUG) {
+                        Slogf.d(LOG_TAG, "onWindowsForAccessibilityChanged(): updating windows for "
+                                + "display %d and token %s",
+                                topFocusedDisplayId, topFocusedWindowToken);
+                    }
                     cacheWindows(windows);
                     // Lets the policy update the focused and active windows.
                     updateWindowsLocked(mAccessibilityUserManager.getCurrentUserIdLocked(),
@@ -443,6 +464,11 @@
                     // Someone may be waiting for the windows - advertise it.
                     mLock.notifyAll();
                 }
+                else if (DEBUG) {
+                    Slogf.d(LOG_TAG, "onWindowsForAccessibilityChanged(): NOT updating windows for "
+                            + "display %d and token %s",
+                            topFocusedDisplayId, topFocusedWindowToken);
+                }
             }
         }
 
@@ -472,6 +498,12 @@
             }
 
             final int windowCount = windows.size();
+            if (VERBOSE) {
+                Slogf.v(LOG_TAG,
+                        "shouldUpdateWindowsLocked(): mDisplayId=%d, windowCount=%d, "
+                        + "mCachedWindowInfos.size()=%d, windows.size()=%d", mDisplayId,
+                        windowCount, mCachedWindowInfos.size(), windows.size());
+            }
             // We computed the windows and if they changed notify the client.
             if (mCachedWindowInfos.size() != windowCount) {
                 // Different size means something changed.
@@ -1274,7 +1306,7 @@
      */
     @Nullable
     public RemoteAccessibilityConnection getConnectionLocked(int userId, int windowId) {
-        if (DEBUG) {
+        if (VERBOSE) {
             Slog.i(LOG_TAG, "Trying to get interaction connection to windowId: " + windowId);
         }
         RemoteAccessibilityConnection connection = mGlobalInteractionConnections.get(windowId);
diff --git a/services/backup/java/com/android/server/backup/BackupAndRestoreFeatureFlags.java b/services/backup/java/com/android/server/backup/BackupAndRestoreFeatureFlags.java
index 1990fe2..98aebdd 100644
--- a/services/backup/java/com/android/server/backup/BackupAndRestoreFeatureFlags.java
+++ b/services/backup/java/com/android/server/backup/BackupAndRestoreFeatureFlags.java
@@ -77,4 +77,19 @@
                 /* name= */ "full_backup_utils_route_buffer_size_bytes",
                 /* defaultValue= */ 32 * 1024); // 32 KB
     }
+
+    /**
+     * Retrieves the value of the flag
+     * "unified_restore_continue_after_transport_failure_in_kv_restore".
+     * If true, Unified restore task will continue to next package if key-value restore of a
+     * package fails due to Transport-level failure. See b/128499560 for more context.
+     */
+    @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
+    public static boolean getUnifiedRestoreContinueAfterTransportFailureInKvRestore() {
+        return DeviceConfig.getBoolean(
+                NAMESPACE,
+                /* name= */
+                "unified_restore_continue_after_transport_failure_in_kv_restore",
+                /* defaultValue= */ true);
+    }
 }
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index 18e28de..1656b6f 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -57,6 +57,7 @@
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
 import com.android.server.backup.BackupAgentTimeoutParameters;
+import com.android.server.backup.BackupAndRestoreFeatureFlags;
 import com.android.server.backup.BackupRestoreTask;
 import com.android.server.backup.BackupUtils;
 import com.android.server.backup.OperationStorage;
@@ -168,11 +169,13 @@
     private final BackupEligibilityRules mBackupEligibilityRules;
 
     @VisibleForTesting
-    PerformUnifiedRestoreTask(UserBackupManagerService backupManagerService) {
+    PerformUnifiedRestoreTask(
+            UserBackupManagerService backupManagerService,
+            TransportConnection transportConnection) {
         mListener = null;
         mAgentTimeoutParameters = null;
         mOperationStorage = null;
-        mTransportConnection = null;
+        mTransportConnection = transportConnection;
         mTransportManager = null;
         mEphemeralOpToken = 0;
         mUserId = 0;
@@ -731,13 +734,18 @@
                             ParcelFileDescriptor.MODE_TRUNCATE);
 
             if (transport.getRestoreData(stage) != BackupTransport.TRANSPORT_OK) {
-                // Transport-level failure, so we wind everything up and
-                // terminate the restore operation.
+                // Transport-level failure. This failure could be specific to package currently in
+                // restore.
                 Slog.e(TAG, "Error getting restore data for " + packageName);
                 EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
                 stage.close();
                 downloadFile.delete();
-                executeNextState(UnifiedRestoreState.FINAL);
+                UnifiedRestoreState nextState =
+                        BackupAndRestoreFeatureFlags
+                                .getUnifiedRestoreContinueAfterTransportFailureInKvRestore()
+                                ? UnifiedRestoreState.RUNNING_QUEUE
+                                : UnifiedRestoreState.FINAL;
+                executeNextState(nextState);
                 return;
             }
 
@@ -1358,6 +1366,7 @@
         executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
     }
 
+    @VisibleForTesting
     void executeNextState(UnifiedRestoreState nextState) {
         if (MORE_DEBUG) {
             Slog.i(TAG, " => executing next step on "
@@ -1369,6 +1378,26 @@
         backupManagerService.getBackupHandler().sendMessage(msg);
     }
 
+    @VisibleForTesting
+    UnifiedRestoreState getCurrentUnifiedRestoreStateForTesting() {
+        return mState;
+    }
+
+    @VisibleForTesting
+    void setCurrentUnifiedRestoreStateForTesting(UnifiedRestoreState state) {
+        mState = state;
+    }
+
+    @VisibleForTesting
+    void setStateDirForTesting(File stateDir) {
+        mStateDir = stateDir;
+    }
+
+    @VisibleForTesting
+    void initiateOneRestoreForTesting(PackageInfo app, long appVersionCode) {
+        initiateOneRestore(app, appVersionCode);
+    }
+
     // restore observer support
     void sendStartRestore(int numPackages) {
         if (mObserver != null) {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 291c0587..9644642 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -151,15 +151,14 @@
     }
 
     void onCameraAccessBlocked(int appUid) {
-        synchronized (mVirtualDeviceManagerLock) {
-            for (int i = 0; i < mVirtualDevices.size(); i++) {
-                CharSequence deviceName = mVirtualDevices.valueAt(i).getDisplayName();
-                mVirtualDevices.valueAt(i).showToastWhereUidIsRunning(appUid,
-                        getContext().getString(
-                                com.android.internal.R.string.vdm_camera_access_denied,
-                                deviceName),
-                        Toast.LENGTH_LONG, Looper.myLooper());
-            }
+        ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot();
+        for (int i = 0; i < virtualDevicesSnapshot.size(); i++) {
+            VirtualDeviceImpl virtualDevice = virtualDevicesSnapshot.get(i);
+            virtualDevice.showToastWhereUidIsRunning(appUid,
+                    getContext().getString(
+                            com.android.internal.R.string.vdm_camera_access_denied,
+                            virtualDevice.getDisplayName()),
+                    Toast.LENGTH_LONG, Looper.myLooper());
         }
     }
 
@@ -265,6 +264,16 @@
         cdm.removeOnAssociationsChangedListener(mCdmAssociationListener);
     }
 
+    private ArrayList<VirtualDeviceImpl> getVirtualDevicesSnapshot() {
+        synchronized (mVirtualDeviceManagerLock) {
+            ArrayList<VirtualDeviceImpl> virtualDevices = new ArrayList<>(mVirtualDevices.size());
+            for (int i = 0; i < mVirtualDevices.size(); i++) {
+                virtualDevices.add(mVirtualDevices.valueAt(i));
+            }
+            return virtualDevices;
+        }
+    }
+
     class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub {
 
         private final VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback =
@@ -314,6 +323,17 @@
             Objects.requireNonNull(activityListener);
             Objects.requireNonNull(soundEffectListener);
 
+            final UserHandle userHandle = getCallingUserHandle();
+            final CameraAccessController cameraAccessController =
+                    getCameraAccessController(userHandle);
+            final int deviceId = sNextUniqueIndex.getAndIncrement();
+            final Consumer<ArraySet<Integer>> runningAppsChangedCallback =
+                    runningUids -> notifyRunningAppsChanged(deviceId, runningUids);
+            VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(getContext(),
+                    associationInfo, VirtualDeviceManagerService.this, token, callingUid,
+                    deviceId, cameraAccessController,
+                    mPendingTrampolineCallback, activityListener,
+                    soundEffectListener, runningAppsChangedCallback, params);
             synchronized (mVirtualDeviceManagerLock) {
                 if (mVirtualDevices.size() == 0) {
                     final long callindId = Binder.clearCallingIdentity();
@@ -323,21 +343,9 @@
                         Binder.restoreCallingIdentity(callindId);
                     }
                 }
-
-                final UserHandle userHandle = getCallingUserHandle();
-                final CameraAccessController cameraAccessController =
-                        getCameraAccessController(userHandle);
-                final int deviceId = sNextUniqueIndex.getAndIncrement();
-                final Consumer<ArraySet<Integer>> runningAppsChangedCallback =
-                        runningUids -> notifyRunningAppsChanged(deviceId, runningUids);
-                VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(getContext(),
-                        associationInfo, VirtualDeviceManagerService.this, token, callingUid,
-                        deviceId, cameraAccessController,
-                        mPendingTrampolineCallback, activityListener,
-                        soundEffectListener, runningAppsChangedCallback, params);
                 mVirtualDevices.put(deviceId, virtualDevice);
-                return virtualDevice;
             }
+            return virtualDevice;
         }
 
         @Override // Binder call
@@ -399,12 +407,11 @@
             if (displayId == Display.INVALID_DISPLAY || displayId == Display.DEFAULT_DISPLAY) {
                 return Context.DEVICE_ID_DEFAULT;
             }
-            synchronized (mVirtualDeviceManagerLock) {
-                for (int i = 0; i < mVirtualDevices.size(); i++) {
-                    VirtualDeviceImpl virtualDevice = mVirtualDevices.valueAt(i);
-                    if (virtualDevice.isDisplayOwnedByVirtualDevice(displayId)) {
-                        return virtualDevice.getDeviceId();
-                    }
+            ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot();
+            for (int i = 0; i < virtualDevicesSnapshot.size(); i++) {
+                VirtualDeviceImpl virtualDevice = virtualDevicesSnapshot.get(i);
+                if (virtualDevice.isDisplayOwnedByVirtualDevice(displayId)) {
+                    return virtualDevice.getDeviceId();
                 }
             }
             return Context.DEVICE_ID_DEFAULT;
@@ -496,10 +503,9 @@
                 return;
             }
             fout.println("Created virtual devices: ");
-            synchronized (mVirtualDeviceManagerLock) {
-                for (int i = 0; i < mVirtualDevices.size(); i++) {
-                    mVirtualDevices.valueAt(i).dump(fd, fout, args);
-                }
+            ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot();
+            for (int i = 0; i < virtualDevicesSnapshot.size(); i++) {
+                virtualDevicesSnapshot.get(i).dump(fd, fout, args);
             }
         }
     }
@@ -516,33 +522,30 @@
 
         @Override
         public int getDeviceOwnerUid(int deviceId) {
+            VirtualDeviceImpl virtualDevice;
             synchronized (mVirtualDeviceManagerLock) {
-                VirtualDeviceImpl virtualDevice = mVirtualDevices.get(deviceId);
-                return virtualDevice != null ? virtualDevice.getOwnerUid() : Process.INVALID_UID;
+                virtualDevice = mVirtualDevices.get(deviceId);
             }
+            return virtualDevice != null ? virtualDevice.getOwnerUid() : Process.INVALID_UID;
         }
 
         @Override
         public @Nullable VirtualSensor getVirtualSensor(int deviceId, int handle) {
+            VirtualDeviceImpl virtualDevice;
             synchronized (mVirtualDeviceManagerLock) {
-                VirtualDeviceImpl virtualDevice = mVirtualDevices.get(deviceId);
-                if (virtualDevice != null) {
-                    return virtualDevice.getVirtualSensorByHandle(handle);
-                }
+                virtualDevice = mVirtualDevices.get(deviceId);
             }
-            return null;
+            return virtualDevice != null ? virtualDevice.getVirtualSensorByHandle(handle) : null;
         }
 
         @Override
         public @NonNull ArraySet<Integer> getDeviceIdsForUid(int uid) {
+            ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot();
             ArraySet<Integer> result = new ArraySet<>();
-            synchronized (mVirtualDeviceManagerLock) {
-                int size = mVirtualDevices.size();
-                for (int i = 0; i < size; i++) {
-                    VirtualDeviceImpl device = mVirtualDevices.valueAt(i);
-                    if (device.isAppRunningOnVirtualDevice(uid)) {
-                        result.add(device.getDeviceId());
-                    }
+            for (int i = 0; i < virtualDevicesSnapshot.size(); i++) {
+                VirtualDeviceImpl device = virtualDevicesSnapshot.get(i);
+                if (device.isAppRunningOnVirtualDevice(uid)) {
+                    result.add(device.getDeviceId());
                 }
             }
             return result;
@@ -630,12 +633,10 @@
 
         @Override
         public boolean isAppRunningOnAnyVirtualDevice(int uid) {
-            synchronized (mVirtualDeviceManagerLock) {
-                int size = mVirtualDevices.size();
-                for (int i = 0; i < size; i++) {
-                    if (mVirtualDevices.valueAt(i).isAppRunningOnVirtualDevice(uid)) {
-                        return true;
-                    }
+            ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot();
+            for (int i = 0; i < virtualDevicesSnapshot.size(); i++) {
+                if (virtualDevicesSnapshot.get(i).isAppRunningOnVirtualDevice(uid)) {
+                    return true;
                 }
             }
             return false;
@@ -643,12 +644,10 @@
 
         @Override
         public boolean isDisplayOwnedByAnyVirtualDevice(int displayId) {
-            synchronized (mVirtualDeviceManagerLock) {
-                int size = mVirtualDevices.size();
-                for (int i = 0; i < size; i++) {
-                    if (mVirtualDevices.valueAt(i).isDisplayOwnedByVirtualDevice(displayId)) {
-                        return true;
-                    }
+            ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot();
+            for (int i = 0; i < virtualDevicesSnapshot.size(); i++) {
+                if (virtualDevicesSnapshot.get(i).isDisplayOwnedByVirtualDevice(displayId)) {
+                    return true;
                 }
             }
             return false;
diff --git a/services/core/Android.bp b/services/core/Android.bp
index c8caab9..cfdf3ac 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -174,7 +174,6 @@
         "android.hardware.configstore-V1.1-java",
         "android.hardware.ir-V1-java",
         "android.hardware.rebootescrow-V1-java",
-        "android.hardware.soundtrigger-V2.3-java",
         "android.hardware.power.stats-V2-java",
         "android.hardware.power-V4-java",
         "android.hidl.manager-V1.2-java",
diff --git a/services/core/java/android/os/BatteryStatsInternal.java b/services/core/java/android/os/BatteryStatsInternal.java
index c6f63dd..12ee131 100644
--- a/services/core/java/android/os/BatteryStatsInternal.java
+++ b/services/core/java/android/os/BatteryStatsInternal.java
@@ -39,12 +39,14 @@
     public static final int CPU_WAKEUP_SUBSYSTEM_UNKNOWN = -1;
     public static final int CPU_WAKEUP_SUBSYSTEM_ALARM = 1;
     public static final int CPU_WAKEUP_SUBSYSTEM_WIFI = 2;
+    public static final int CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER = 3;
 
     /** @hide */
     @IntDef(prefix = {"CPU_WAKEUP_SUBSYSTEM_"}, value = {
             CPU_WAKEUP_SUBSYSTEM_UNKNOWN,
             CPU_WAKEUP_SUBSYSTEM_ALARM,
             CPU_WAKEUP_SUBSYSTEM_WIFI,
+            CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER,
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface CpuWakeupSubsystem {
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 3ecf933..8fc30e4 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -80,7 +80,7 @@
 import android.util.apk.ApkSigningBlockUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.expresslog.Histogram;
+import com.android.modules.expresslog.Histogram;
 import com.android.internal.os.IBinaryTransparencyService;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.pm.ApexManager;
diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java
index 3487613..5156c54 100644
--- a/services/core/java/com/android/server/DockObserver.java
+++ b/services/core/java/com/android/server/DockObserver.java
@@ -70,7 +70,7 @@
 
     private boolean mUpdatesStopped;
 
-    private final boolean mKeepDreamingWhenUndocking;
+    private final boolean mKeepDreamingWhenUnplugging;
     private final boolean mAllowTheaterModeWakeFromDock;
 
     private final List<ExtconStateConfig> mExtconStateConfigs;
@@ -167,8 +167,8 @@
         mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
         mAllowTheaterModeWakeFromDock = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_allowTheaterModeWakeFromDock);
-        mKeepDreamingWhenUndocking = context.getResources().getBoolean(
-                com.android.internal.R.bool.config_keepDreamingWhenUndocking);
+        mKeepDreamingWhenUnplugging = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_keepDreamingWhenUnplugging);
         mDeviceProvisionedObserver = new DeviceProvisionedObserver(mHandler);
 
         mExtconStateConfigs = loadExtconStateConfigs(context);
@@ -237,7 +237,7 @@
     }
 
     private boolean allowWakeFromDock() {
-        if (mKeepDreamingWhenUndocking) {
+        if (mKeepDreamingWhenUnplugging) {
             return false;
         }
         return (mAllowTheaterModeWakeFromDock
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index 409f054..123cd328 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -27,6 +27,7 @@
 per-file **IpSec* = file:/services/core/java/com/android/server/vcn/OWNERS
 per-file *Location* = file:/services/core/java/com/android/server/location/OWNERS
 per-file *Network* = file:/services/core/java/com/android/server/net/OWNERS
+per-file *SoundTrigger* = file:/media/java/android/media/soundtrigger/OWNERS
 per-file *Storage* = file:/core/java/android/os/storage/OWNERS
 per-file *TimeUpdate* = file:/services/core/java/com/android/server/timezonedetector/OWNERS
 per-file DynamicSystemService.java = file:/packages/DynamicSystemInstallationService/OWNERS
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 92889c0..d256aea 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -426,7 +426,7 @@
                                 }
                                 int impact = registeredObserver.onHealthCheckFailed(
                                         versionedPackage, failureReason, mitigationCount);
-                                if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
+                                if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
                                         && impact < currentObserverImpact) {
                                     currentObserverToNotify = registeredObserver;
                                     currentObserverImpact = impact;
@@ -466,7 +466,7 @@
             if (registeredObserver != null) {
                 int impact = registeredObserver.onHealthCheckFailed(
                         failingPackage, failureReason, 1);
-                if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
+                if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
                         && impact < currentObserverImpact) {
                     currentObserverToNotify = registeredObserver;
                     currentObserverImpact = impact;
@@ -494,7 +494,7 @@
                     PackageHealthObserver registeredObserver = observer.registeredObserver;
                     if (registeredObserver != null) {
                         int impact = registeredObserver.onBootLoop(mitigationCount);
-                        if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
+                        if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
                                 && impact < currentObserverImpact) {
                             currentObserverToNotify = registeredObserver;
                             currentObserverImpact = impact;
@@ -576,19 +576,23 @@
 
     /** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}. */
     @Retention(SOURCE)
-    @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_NONE,
-                     PackageHealthObserverImpact.USER_IMPACT_LOW,
-                     PackageHealthObserverImpact.USER_IMPACT_MEDIUM,
-                     PackageHealthObserverImpact.USER_IMPACT_HIGH})
+    @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
+                     PackageHealthObserverImpact.USER_IMPACT_LEVEL_10,
+                     PackageHealthObserverImpact.USER_IMPACT_LEVEL_30,
+                     PackageHealthObserverImpact.USER_IMPACT_LEVEL_50,
+                     PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
+                     PackageHealthObserverImpact.USER_IMPACT_LEVEL_100})
     public @interface PackageHealthObserverImpact {
         /** No action to take. */
-        int USER_IMPACT_NONE = 0;
+        int USER_IMPACT_LEVEL_0 = 0;
         /* Action has low user impact, user of a device will barely notice. */
-        int USER_IMPACT_LOW = 1;
-        /* Action has medium user impact, user of a device will likely notice. */
-        int USER_IMPACT_MEDIUM = 3;
+        int USER_IMPACT_LEVEL_10 = 10;
+        /* Actions having medium user impact, user of a device will likely notice. */
+        int USER_IMPACT_LEVEL_30 = 30;
+        int USER_IMPACT_LEVEL_50 = 50;
+        int USER_IMPACT_LEVEL_70 = 70;
         /* Action has high user impact, a last resort, user of a device will be very frustrated. */
-        int USER_IMPACT_HIGH = 5;
+        int USER_IMPACT_LEVEL_100 = 100;
     }
 
     /** Register instances of this interface to receive notifications on package failure. */
@@ -633,7 +637,7 @@
          *                        boot loop (including this time).
          */
         default @PackageHealthObserverImpact int onBootLoop(int mitigationCount) {
-            return PackageHealthObserverImpact.USER_IMPACT_NONE;
+            return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
         }
 
         /**
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index 3de65f9..6e2e5f7 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -107,7 +107,7 @@
     static final String NAMESPACE_TO_PACKAGE_MAPPING_FLAG =
             "namespace_to_package_mapping";
     @VisibleForTesting
-    static final long FACTORY_RESET_THROTTLE_DURATION_MS = TimeUnit.MINUTES.toMillis(10);
+    static final long DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN = 10;
 
     private static final String NAME = "rescue-party-observer";
 
@@ -117,6 +117,8 @@
             "persist.device_config.configuration.disable_rescue_party";
     private static final String PROP_DISABLE_FACTORY_RESET_FLAG =
             "persist.device_config.configuration.disable_rescue_party_factory_reset";
+    private static final String PROP_THROTTLE_DURATION_MIN_FLAG =
+            "persist.device_config.configuration.rescue_party_throttle_duration_min";
 
     private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
             | ApplicationInfo.FLAG_SYSTEM;
@@ -489,13 +491,14 @@
         switch(rescueLevel) {
             case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
             case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
-                return PackageHealthObserverImpact.USER_IMPACT_LOW;
+                return PackageHealthObserverImpact.USER_IMPACT_LEVEL_10;
             case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
             case LEVEL_WARM_REBOOT:
+                return PackageHealthObserverImpact.USER_IMPACT_LEVEL_50;
             case LEVEL_FACTORY_RESET:
-                return PackageHealthObserverImpact.USER_IMPACT_HIGH;
+                return PackageHealthObserverImpact.USER_IMPACT_LEVEL_100;
             default:
-                return PackageHealthObserverImpact.USER_IMPACT_NONE;
+                return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
         }
     }
 
@@ -633,7 +636,7 @@
                 return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount,
                         mayPerformReboot(failedPackage)));
             } else {
-                return PackageHealthObserverImpact.USER_IMPACT_NONE;
+                return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
             }
         }
 
@@ -677,7 +680,7 @@
         @Override
         public int onBootLoop(int mitigationCount) {
             if (isDisabled()) {
-                return PackageHealthObserverImpact.USER_IMPACT_NONE;
+                return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
             }
             return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, true));
         }
@@ -721,7 +724,9 @@
         private boolean shouldThrottleReboot() {
             Long lastResetTime = SystemProperties.getLong(PROP_LAST_FACTORY_RESET_TIME_MS, 0);
             long now = System.currentTimeMillis();
-            return now < lastResetTime + FACTORY_RESET_THROTTLE_DURATION_MS;
+            long throttleDurationMin = SystemProperties.getLong(PROP_THROTTLE_DURATION_MIN_FLAG,
+                    DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN);
+            return now < lastResetTime + TimeUnit.MINUTES.toMillis(throttleDurationMin);
         }
 
         private boolean isPersistentSystemApp(@NonNull String packageName) {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java b/services/core/java/com/android/server/SoundTriggerInternal.java
similarity index 97%
rename from services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
rename to services/core/java/com/android/server/SoundTriggerInternal.java
index cc398d9..e6c1750 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
+++ b/services/core/java/com/android/server/SoundTriggerInternal.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.soundtrigger;
+package com.android.server;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -29,15 +29,13 @@
 import android.media.permission.Identity;
 import android.os.IBinder;
 
-import com.android.server.voiceinteraction.VoiceInteractionManagerService;
-
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.List;
 
 /**
  * Provides a local service for managing voice-related recoginition models. This is primarily used
- * by the {@link VoiceInteractionManagerService}.
+ * by the {@code VoiceInteractionManagerService}.
  */
 public interface SoundTriggerInternal {
     /**
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 7b618b1..ee18ed5 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -26,6 +26,17 @@
 import static android.app.ActivityManager.PROCESS_STATE_HEAVY_WEIGHT;
 import static android.app.ActivityManager.PROCESS_STATE_RECEIVER;
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BIND_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_STOP_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UID_IDLE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UNBIND_SERVICE;
 import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_DEPRECATED;
 import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_DISABLED;
 import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_OK;
@@ -117,6 +128,7 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
+import android.app.ActivityManagerInternal.OomAdjReason;
 import android.app.ActivityManagerInternal.ServiceNotificationPolicy;
 import android.app.ActivityThread;
 import android.app.AppGlobals;
@@ -1007,6 +1019,24 @@
         r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
                 service, neededGrants, callingUid, callingProcessName, callingPackage));
 
+        // We want to allow scheduling user-initiated jobs when the app is running a
+        // foreground service that was started in the same conditions that allows for scheduling
+        // UI jobs. More explicitly, we want to allow scheduling UI jobs when the app is running
+        // an FGS that started when the app was in the TOP or a BAL-approved state.
+        final boolean isFgs = r.isForeground || r.fgRequired;
+        if (isFgs) {
+            // As of Android UDC, the conditions required for the while-in-use permissions
+            // are the same conditions that we want, so we piggyback on that logic.
+            // Use that as a shortcut if possible to avoid having to recheck all the conditions.
+            final boolean whileInUseAllowsUiJobScheduling =
+                    ActivityManagerService.doesReasonCodeAllowSchedulingUserInitiatedJobs(
+                            r.mAllowWhileInUsePermissionInFgsReason);
+            r.updateAllowUiJobScheduling(whileInUseAllowsUiJobScheduling
+                    || mAm.canScheduleUserInitiatedJobs(callingUid, callingPid, callingPackage));
+        } else {
+            r.updateAllowUiJobScheduling(false);
+        }
+
         if (fgRequired) {
             // We are now effectively running a foreground service.
             synchronized (mAm.mProcessStats.mLock) {
@@ -1146,7 +1176,7 @@
                                             } finally {
                                                 /* Will be a no-op if nothing pending */
                                                 mAm.updateOomAdjPendingTargetsLocked(
-                                                        OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+                                                        OOM_ADJ_REASON_START_SERVICE);
                                             }
                                         } else {
                                             unbindServiceLocked(connection);
@@ -1236,8 +1266,7 @@
                             /* ignore - local call */
                         } finally {
                             /* Will be a no-op if nothing pending */
-                            mAm.updateOomAdjPendingTargetsLocked(
-                                    OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+                            mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
                         }
                     } else { // Starting a service
                         try {
@@ -1311,7 +1340,7 @@
                 false /* packageFrozen */,
                 true /* enqueueOomAdj */);
         /* Will be a no-op if nothing pending */
-        mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+        mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
         if (error != null) {
             return new ComponentName("!!", error);
         }
@@ -1496,7 +1525,7 @@
                     stopServiceLocked(service, true);
                 }
                 if (size > 0) {
-                    mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                    mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_UID_IDLE);
                 }
             }
         }
@@ -3296,7 +3325,7 @@
 
             Slog.e(TAG_SERVICE, "Short FGS procstate demoted: " + sr);
 
-            mAm.updateOomAdjLocked(sr.app, OomAdjuster.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT);
+            mAm.updateOomAdjLocked(sr.app, OOM_ADJ_REASON_SHORT_FGS_TIMEOUT);
         }
     }
 
@@ -3630,7 +3659,7 @@
                 needOomAdj = true;
                 if (bringUpServiceLocked(s, service.getFlags(), callerFg, false,
                         permissionsReviewRequired, packageFrozen, true) != null) {
-                    mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE);
+                    mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_BIND_SERVICE);
                     return 0;
                 }
             }
@@ -3655,7 +3684,7 @@
                 mAm.enqueueOomAdjTargetLocked(s.app);
             }
             if (needOomAdj) {
-                mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE);
+                mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_BIND_SERVICE);
             }
 
             final int packageState = wasStopped
@@ -3787,7 +3816,8 @@
                     }
                 }
 
-                serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false, false);
+                serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false, false,
+                        OOM_ADJ_REASON_EXECUTING_SERVICE);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -3878,7 +3908,7 @@
                 }
             }
 
-            mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+            mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_UNBIND_SERVICE);
 
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
@@ -3925,7 +3955,8 @@
                     }
                 }
 
-                serviceDoneExecutingLocked(r, inDestroying, false, false);
+                serviceDoneExecutingLocked(r, inDestroying, false, false,
+                        OOM_ADJ_REASON_UNBIND_SERVICE);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -4360,11 +4391,11 @@
     /**
      * Bump the given service record into executing state.
      * @param oomAdjReason The caller requests it to perform the oomAdjUpdate not {@link
-     *         OomAdjuster#OOM_ADJ_REASON_NONE}.
+     *         ActivityManagerInternal#OOM_ADJ_REASON_NONE}.
      * @return {@code true} if it performed oomAdjUpdate.
      */
     private boolean bumpServiceExecutingLocked(
-            ServiceRecord r, boolean fg, String why, @OomAdjuster.OomAdjReason int oomAdjReason) {
+            ServiceRecord r, boolean fg, String why, @OomAdjReason int oomAdjReason) {
         if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, ">>> EXECUTING "
                 + why + " of " + r + " in app " + r.app);
         else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING, ">>> EXECUTING "
@@ -4416,7 +4447,7 @@
             }
         }
         boolean oomAdjusted = false;
-        if (oomAdjReason != OomAdjuster.OOM_ADJ_REASON_NONE && r.app != null
+        if (oomAdjReason != OOM_ADJ_REASON_NONE && r.app != null
                 && r.app.mState.getCurProcState() > ActivityManager.PROCESS_STATE_SERVICE) {
             // Force an immediate oomAdjUpdate, so the client app could be in the correct process
             // state before doing any service related transactions
@@ -4440,8 +4471,7 @@
                 + " rebind=" + rebind);
         if ((!i.requested || rebind) && i.apps.size() > 0) {
             try {
-                bumpServiceExecutingLocked(r, execInFg, "bind",
-                        OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE);
+                bumpServiceExecutingLocked(r, execInFg, "bind", OOM_ADJ_REASON_BIND_SERVICE);
                 if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                     Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, "requestServiceBinding="
                             + i.intent.getIntent() + ". bindSeq=" + mBindServiceSeqCounter);
@@ -4457,13 +4487,15 @@
                 // Keep the executeNesting count accurate.
                 if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r, e);
                 final boolean inDestroying = mDestroyingServices.contains(r);
-                serviceDoneExecutingLocked(r, inDestroying, inDestroying, false);
+                serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
+                        OOM_ADJ_REASON_UNBIND_SERVICE);
                 throw e;
             } catch (RemoteException e) {
                 if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r);
                 // Keep the executeNesting count accurate.
                 final boolean inDestroying = mDestroyingServices.contains(r);
-                serviceDoneExecutingLocked(r, inDestroying, inDestroying, false);
+                serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
+                        OOM_ADJ_REASON_UNBIND_SERVICE);
                 return false;
             }
         }
@@ -4841,7 +4873,7 @@
             // Ignore, it's been logged and nothing upstack cares.
         } finally {
             /* Will be a no-op if nothing pending */
-            mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+            mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
         }
     }
 
@@ -5193,13 +5225,14 @@
 
         final ProcessServiceRecord psr = app.mServices;
         final boolean newService = psr.startService(r);
-        bumpServiceExecutingLocked(r, execInFg, "create", OomAdjuster.OOM_ADJ_REASON_NONE);
+        bumpServiceExecutingLocked(r, execInFg, "create",
+                OOM_ADJ_REASON_NONE /* use "none" to avoid extra oom adj */);
         mAm.updateLruProcessLocked(app, false, null);
         updateServiceForegroundLocked(psr, /* oomAdj= */ false);
         // Force an immediate oomAdjUpdate, so the client app could be in the correct process state
         // before doing any service related transactions
         mAm.enqueueOomAdjTargetLocked(app);
-        mAm.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+        mAm.updateOomAdjLocked(app, OOM_ADJ_REASON_START_SERVICE);
 
         boolean created = false;
         try {
@@ -5233,7 +5266,8 @@
             if (!created) {
                 // Keep the executeNesting count accurate.
                 final boolean inDestroying = mDestroyingServices.contains(r);
-                serviceDoneExecutingLocked(r, inDestroying, inDestroying, false);
+                serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
+                        OOM_ADJ_REASON_STOP_SERVICE);
 
                 // Cleanup.
                 if (newService) {
@@ -5319,7 +5353,8 @@
             mAm.grantImplicitAccess(r.userId, si.intent, si.callingId,
                     UserHandle.getAppId(r.appInfo.uid)
             );
-            bumpServiceExecutingLocked(r, execInFg, "start", OomAdjuster.OOM_ADJ_REASON_NONE);
+            bumpServiceExecutingLocked(r, execInFg, "start",
+                    OOM_ADJ_REASON_NONE /* use "none" to avoid extra oom adj */);
             if (r.fgRequired && !r.fgWaiting) {
                 if (!r.isForeground) {
                     if (DEBUG_BACKGROUND_CHECK) {
@@ -5345,7 +5380,7 @@
 
         if (!oomAdjusted) {
             mAm.enqueueOomAdjTargetLocked(r.app);
-            mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+            mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
         }
         ParceledListSlice<ServiceStartArgs> slice = new ParceledListSlice<>(args);
         slice.setInlineCountLimit(4);
@@ -5371,10 +5406,11 @@
             // Keep nesting count correct
             final boolean inDestroying = mDestroyingServices.contains(r);
             for (int i = 0, size = args.size(); i < size; i++) {
-                serviceDoneExecutingLocked(r, inDestroying, inDestroying, true);
+                serviceDoneExecutingLocked(r, inDestroying, inDestroying, true,
+                        OOM_ADJ_REASON_STOP_SERVICE);
             }
             /* Will be a no-op if nothing pending */
-            mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+            mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_STOP_SERVICE);
             if (caughtException instanceof TransactionTooLargeException) {
                 throw (TransactionTooLargeException)caughtException;
             }
@@ -5461,7 +5497,7 @@
                 if (ibr.hasBound) {
                     try {
                         oomAdjusted |= bumpServiceExecutingLocked(r, false, "bring down unbind",
-                                OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                                OOM_ADJ_REASON_UNBIND_SERVICE);
                         ibr.hasBound = false;
                         ibr.requested = false;
                         r.app.getThread().scheduleUnbindService(r,
@@ -5615,7 +5651,7 @@
                 } else {
                     try {
                         oomAdjusted |= bumpServiceExecutingLocked(r, false, "destroy",
-                                oomAdjusted ? 0 : OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                                oomAdjusted ? 0 : OOM_ADJ_REASON_STOP_SERVICE);
                         mDestroyingServices.add(r);
                         r.destroying = true;
                         r.app.getThread().scheduleStopService(r);
@@ -5637,7 +5673,7 @@
         if (!oomAdjusted) {
             mAm.enqueueOomAdjTargetLocked(r.app);
             if (!enqueueOomAdj) {
-                mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_STOP_SERVICE);
             }
         }
         if (r.bindings.size() > 0) {
@@ -5762,8 +5798,7 @@
             if (s.app != null && s.app.getThread() != null && b.intent.apps.size() == 0
                     && b.intent.hasBound) {
                 try {
-                    bumpServiceExecutingLocked(s, false, "unbind",
-                            OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                    bumpServiceExecutingLocked(s, false, "unbind", OOM_ADJ_REASON_UNBIND_SERVICE);
                     if (b.client != s.app && c.notHasFlag(Context.BIND_WAIVE_PRIORITY)
                             && s.app.mState.getSetProcState() <= PROCESS_STATE_HEAVY_WEIGHT) {
                         // If this service's process is not already in the cached list,
@@ -5886,7 +5921,8 @@
                 }
             }
             final long origId = Binder.clearCallingIdentity();
-            serviceDoneExecutingLocked(r, inDestroying, inDestroying, enqueueOomAdj);
+            serviceDoneExecutingLocked(r, inDestroying, inDestroying, enqueueOomAdj,
+                    OOM_ADJ_REASON_EXECUTING_SERVICE);
             Binder.restoreCallingIdentity(origId);
         } else {
             Slog.w(TAG, "Done executing unknown service from pid "
@@ -5905,11 +5941,11 @@
                 r.tracker.setStarted(false, memFactor, now);
             }
         }
-        serviceDoneExecutingLocked(r, true, true, enqueueOomAdj);
+        serviceDoneExecutingLocked(r, true, true, enqueueOomAdj, OOM_ADJ_REASON_PROCESS_END);
     }
 
     private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
-            boolean finishing, boolean enqueueOomAdj) {
+            boolean finishing, boolean enqueueOomAdj, @OomAdjReason int oomAdjReason) {
         if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "<<< DONE EXECUTING " + r
                 + ": nesting=" + r.executeNesting
                 + ", inDestroying=" + inDestroying + ", app=" + r.app);
@@ -5945,7 +5981,7 @@
                 if (enqueueOomAdj) {
                     mAm.enqueueOomAdjTargetLocked(r.app);
                 } else {
-                    mAm.updateOomAdjLocked(r.app, OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                    mAm.updateOomAdjLocked(r.app, oomAdjReason);
                 }
             }
             r.executeFg = false;
@@ -6015,7 +6051,7 @@
                         bringDownServiceLocked(sr, true);
                     }
                     /* Will be a no-op if nothing pending */
-                    mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+                    mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
                 }
             } catch (RemoteException e) {
                 Slog.w(TAG, "Exception in new application when starting service "
@@ -6075,7 +6111,7 @@
             }
         }
         if (needOomAdj) {
-            mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+            mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_PROCESS_END);
         }
     }
 
@@ -6146,7 +6182,7 @@
                 bringDownServiceLocked(mTmpCollectionResults.get(i), true);
             }
             if (size > 0) {
-                mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_COMPONENT_DISABLED);
             }
             if (fullStop && !mTmpCollectionResults.isEmpty()) {
                 // if we're tearing down the app's entire service state, account for possible
@@ -6273,7 +6309,7 @@
             }
         }
         if (needOomAdj) {
-            mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+            mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_REMOVE_TASK);
         }
     }
 
@@ -6444,7 +6480,7 @@
             }
         }
 
-        mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+        mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_STOP_SERVICE);
 
         if (!allowRestart) {
             psr.stopAllServices();
@@ -7344,26 +7380,12 @@
         } else {
             allowWhileInUse = REASON_UNKNOWN;
         }
-        // We want to allow scheduling user-initiated jobs when the app is running a
-        // foreground service that was started in the same conditions that allows for scheduling
-        // UI jobs. More explicitly, we want to allow scheduling UI jobs when the app is running
-        // an FGS that started when the app was in the TOP or a BAL-approved state.
-        // As of Android UDC, the conditions required for the while-in-use permissions
-        // are the same conditions that we want, so we piggyback on that logic.
-        // We use that as a shortcut if possible so we don't have to recheck all the conditions.
-        final boolean isFgs = r.isForeground || r.fgRequired;
-        if (isFgs) {
-            r.updateAllowUiJobScheduling(ActivityManagerService
-                    .doesReasonCodeAllowSchedulingUserInitiatedJobs(allowWhileInUse)
-                    || mAm.canScheduleUserInitiatedJobs(
-                            callingUid, callingPid, callingPackage, true));
-        } else {
-            r.updateAllowUiJobScheduling(false);
-        }
+        r.mAllowWhileInUsePermissionInFgsReason = allowWhileInUse;
     }
 
     void resetFgsRestrictionLocked(ServiceRecord r) {
         r.mAllowWhileInUsePermissionInFgs = false;
+        r.mAllowWhileInUsePermissionInFgsReason = REASON_DENIED;
         r.mAllowStartForeground = REASON_DENIED;
         r.mInfoAllowStartForeground = null;
         r.mInfoTempFgsAllowListReason = null;
@@ -7407,14 +7429,17 @@
 
         final int uidState = mAm.getUidStateLocked(callingUid);
         if (ret == REASON_DENIED) {
-            // Is the calling UID at PROCESS_STATE_TOP or above?
+            // Allow FGS while-in-use if the caller's process state is PROCESS_STATE_PERSISTENT,
+            // PROCESS_STATE_PERSISTENT_UI or PROCESS_STATE_TOP.
             if (uidState <= PROCESS_STATE_TOP) {
                 ret = getReasonCodeFromProcState(uidState);
             }
         }
 
         if (ret == REASON_DENIED) {
-            // Does the calling UID have any visible activity?
+            // Allow FGS while-in-use if the caller has visible activity.
+            // Here we directly check ActivityTaskManagerService, instead of checking
+            // PendingStartActivityUids in ActivityManagerService, which gives the same result.
             final boolean isCallingUidVisible = mAm.mAtmInternal.isUidForeground(callingUid);
             if (isCallingUidVisible) {
                 ret = REASON_UID_VISIBLE;
@@ -7422,7 +7447,8 @@
         }
 
         if (ret == REASON_DENIED) {
-            // Is the allow activity background start flag on?
+            // Allow FGS while-in-use if the background activity start flag is on. Because
+            // activity start can lead to FGS start in TOP state and obtain while-in-use.
             if (backgroundStartPrivileges.allowsBackgroundActivityStarts()) {
                 ret = REASON_START_ACTIVITY_FLAG;
             }
@@ -7431,6 +7457,7 @@
         if (ret == REASON_DENIED) {
             boolean isCallerSystem = false;
             final int callingAppId = UserHandle.getAppId(callingUid);
+            // Allow FGS while-in-use for a list of special UIDs.
             switch (callingAppId) {
                 case ROOT_UID:
                 case SYSTEM_UID:
@@ -7449,6 +7476,10 @@
         }
 
         if (ret == REASON_DENIED) {
+            // Allow FGS while-in-use if the WindowManager allows background activity start.
+            // This is mainly to get the 10 seconds grace period if any activity in the caller has
+            // either started or finished very recently. The binding flag
+            // BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS is also allowed by the check here.
             final Integer allowedType = mAm.mProcessList.searchEachLruProcessesLOSP(false, pr -> {
                 if (pr.uid == callingUid) {
                     if (pr.getWindowProcessController().areBackgroundFgsStartsAllowed()) {
@@ -7463,6 +7494,12 @@
         }
 
         if (ret == REASON_DENIED) {
+            // Allow FGS while-in-use if the caller UID is in ActivityManagerService's
+            // mFgsWhileInUseTempAllowList. This is a temp allowlist to allow FGS while-in-use. It
+            // is used when MediaSessionService's bluetooth button or play/resume/stop commands are
+            // issued. The typical temp allowlist duration is 10 seconds.
+            // This temp allowlist mechanism can also be called by other system_server internal
+            // components such as Telephone/VOIP if they want to start a FGS and get while-in-use.
             if (mAm.mInternal.isTempAllowlistedForFgsWhileInUse(callingUid)) {
                 return REASON_TEMP_ALLOWED_WHILE_IN_USE;
             }
@@ -7470,6 +7507,8 @@
 
         if (ret == REASON_DENIED) {
             if (targetProcess != null) {
+                // Allow FGS while-in-use if the caller of the instrumentation has
+                // START_ACTIVITIES_FROM_BACKGROUND permission.
                 ActiveInstrumentation instr = targetProcess.getActiveInstrumentation();
                 if (instr != null && instr.mHasBackgroundActivityStartsPermission) {
                     ret = REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION;
@@ -7478,6 +7517,9 @@
         }
 
         if (ret == REASON_DENIED) {
+            // Allow FGS while-in-use if the caller has START_ACTIVITIES_FROM_BACKGROUND
+            // permission, because starting an activity can lead to starting FGS from the TOP state
+            // and obtain while-in-use.
             if (mAm.checkPermission(START_ACTIVITIES_FROM_BACKGROUND, callingPid, callingUid)
                     == PERMISSION_GRANTED) {
                 ret = REASON_BACKGROUND_ACTIVITY_PERMISSION;
@@ -7485,6 +7527,8 @@
         }
 
         if (ret == REASON_DENIED) {
+            // Allow FGS while-in-use if the caller is in the while-in-use allowlist. Right now
+            // AttentionService and SystemCaptionsService packageName are in this allowlist.
             if (verifyPackage(callingPackage, callingUid)) {
                 final boolean isAllowedPackage =
                         mAllowListWhileInUsePermissionInFgs.contains(callingPackage);
@@ -7499,7 +7543,7 @@
         }
 
         if (ret == REASON_DENIED) {
-            // Is the calling UID a device owner app?
+            // Allow FGS while-in-use if the caller is the device owner.
             final boolean isDeviceOwner = mAm.mInternal.isDeviceOwner(callingUid);
             if (isDeviceOwner) {
                 ret = REASON_DEVICE_OWNER;
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index ae5dbe1..44e198b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -213,7 +213,7 @@
     private static final boolean DEFAULT_USE_TIERED_CACHED_ADJ = false;
     private static final long DEFAULT_TIERED_CACHED_ADJ_DECAY_TIME = 60 * 1000;
 
-    private static final boolean DEFAULT_USE_MODERN_TRIM = false;
+    private static final boolean DEFAULT_USE_MODERN_TRIM = true;
 
     /**
      * Same as {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 9752ade..97d34b8 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -44,6 +44,14 @@
 import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
 import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_CREATED;
 import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SYSTEM_INIT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.OP_NONE;
 import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT;
@@ -199,6 +207,7 @@
 import android.app.ActivityManagerInternal.BroadcastEventListener;
 import android.app.ActivityManagerInternal.ForegroundServiceStateListener;
 import android.app.ActivityManagerInternal.MediaProjectionTokenEvent;
+import android.app.ActivityManagerInternal.OomAdjReason;
 import android.app.ActivityTaskManager.RootTaskInfo;
 import android.app.ActivityThread;
 import android.app.AnrController;
@@ -368,7 +377,6 @@
 import android.util.IndentingPrintWriter;
 import android.util.IntArray;
 import android.util.Log;
-import android.util.LogWriter;
 import android.util.Pair;
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
@@ -1968,7 +1976,7 @@
                 app.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_SYSTEM);
                 addPidLocked(app);
                 updateLruProcessLocked(app, false, null);
-                updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+                updateOomAdjLocked(OOM_ADJ_REASON_SYSTEM_INIT);
             }
         } catch (PackageManager.NameNotFoundException e) {
             throw new RuntimeException(
@@ -2502,7 +2510,7 @@
         // bind background threads to little cores
         // this is expected to fail inside of framework tests because apps can't touch cpusets directly
         // make sure we've already adjusted system_server's internal view of itself first
-        updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdjLocked(OOM_ADJ_REASON_SYSTEM_INIT);
         try {
             Process.setThreadGroupAndCpuset(BackgroundThread.get().getThreadId(),
                     Process.THREAD_GROUP_SYSTEM);
@@ -3387,7 +3395,7 @@
             handleAppDiedLocked(app, pid, false, true, fromBinderDied);
 
             if (doOomAdj) {
-                updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_PROCESS_END);
+                updateOomAdjLocked(OOM_ADJ_REASON_PROCESS_END);
             }
             if (doLowMem) {
                 mAppProfiler.doLowMemReportIfNeededLocked(app);
@@ -4843,7 +4851,7 @@
             }
 
             if (!didSomething) {
-                updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
+                updateOomAdjLocked(app, OOM_ADJ_REASON_PROCESS_BEGIN);
                 checkTime(startTime, "finishAttachApplicationInner: after updateOomAdjLocked");
             }
 
@@ -5485,7 +5493,7 @@
                 "setProcessLimit()");
         synchronized (this) {
             mConstants.setOverrideMaxCachedProcesses(max);
-            trimApplicationsLocked(true, OomAdjuster.OOM_ADJ_REASON_PROCESS_END);
+            trimApplicationsLocked(true, OOM_ADJ_REASON_PROCESS_END);
         }
     }
 
@@ -5513,7 +5521,7 @@
                 pr.mState.setForcingToImportant(null);
                 clearProcessForegroundLocked(pr);
             }
-            updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+            updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
         }
     }
 
@@ -5560,7 +5568,7 @@
             }
 
             if (changed) {
-                updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+                updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
             }
         }
     }
@@ -6181,8 +6189,8 @@
 
         final ProcessServiceRecord psr = pr.mServices;
         if (psr != null && psr.hasForegroundServices()) {
-            for (int s = psr.numberOfExecutingServices() - 1; s >= 0; --s) {
-                final ServiceRecord sr = psr.getExecutingServiceAt(s);
+            for (int s = psr.numberOfRunningServices() - 1; s >= 0; --s) {
+                final ServiceRecord sr = psr.getRunningServiceAt(s);
                 if (sr.isForeground && sr.mAllowUiJobScheduling) {
                     return true;
                 }
@@ -6197,12 +6205,7 @@
      * {@link android.app.job.JobInfo.Builder#setUserInitiated(boolean) user-initiated job}.
      */
     // TODO(262260570): log allow reason to an atom
-    private boolean canScheduleUserInitiatedJobs(int uid, int pid, String pkgName) {
-        return canScheduleUserInitiatedJobs(uid, pid, pkgName, false);
-    }
-
-    boolean canScheduleUserInitiatedJobs(int uid, int pid, String pkgName,
-            boolean skipWhileInUseCheck) {
+    boolean canScheduleUserInitiatedJobs(int uid, int pid, String pkgName) {
         synchronized (this) {
             final ProcessRecord processRecord;
             synchronized (mPidsSelfLocked) {
@@ -6232,7 +6235,7 @@
             // As of Android UDC, the conditions required to grant a while-in-use permission
             // covers the majority of those cases, and so we piggyback on that logic as the base.
             // Missing cases are added after.
-            if (!skipWhileInUseCheck && mServices.canAllowWhileInUsePermissionInFgsLocked(
+            if (mServices.canAllowWhileInUsePermissionInFgsLocked(
                     pid, uid, pkgName, processRecord, backgroundStartPrivileges)) {
                 return true;
             }
@@ -6869,7 +6872,7 @@
                     new HostingRecord(HostingRecord.HOSTING_TYPE_ADDED_APPLICATION,
                             customProcess != null ? customProcess : info.processName));
             updateLruProcessLocked(app, false, null);
-            updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
+            updateOomAdjLocked(app, OOM_ADJ_REASON_PROCESS_BEGIN);
         }
 
         // Report usage as process is persistent and being started.
@@ -6986,7 +6989,7 @@
                 mOomAdjProfiler.onWakefulnessChanged(wakefulness);
                 mOomAdjuster.onWakefulnessChanged(wakefulness);
 
-                updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+                updateOomAdjLocked(OOM_ADJ_REASON_UI_VISIBILITY);
             }
         }
     }
@@ -7748,7 +7751,7 @@
                     }
                 }
                 if (changed) {
-                    updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+                    updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
                 }
             }
         } finally {
@@ -8728,7 +8731,9 @@
         // 'recoverable' is that the app doesn't crash). Normally, for nonrecoreable native crashes,
         // debuggerd will terminate the process, but there's a backup where ActivityManager will
         // also kill it. Avoid that.
-        if (!recoverable) {
+        if (recoverable) {
+            mAppErrors.sendRecoverableCrashToAppExitInfo(r, crashInfo);
+        } else {
             mAppErrors.crashApplication(r, crashInfo);
         }
     }
@@ -9508,7 +9513,7 @@
 
             mAppProfiler.setMemFactorOverrideLocked(level);
             // Kick off an oom adj update since we forced a mem factor update.
-            updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+            updateOomAdjLocked(OOM_ADJ_REASON_SHELL);
         }
     }
 
@@ -13408,7 +13413,7 @@
             proc.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_BACKUP);
 
             // Try not to kill the process during backup
-            updateOomAdjLocked(proc, OomAdjuster.OOM_ADJ_REASON_NONE);
+            updateOomAdjLocked(proc, OOM_ADJ_REASON_BACKUP);
 
             // If the process is already attached, schedule the creation of the backup agent now.
             // If it is not yet live, this will be done when it attaches to the framework.
@@ -13532,7 +13537,7 @@
 
                 // Not backing this app up any more; reset its OOM adjustment
                 final ProcessRecord proc = backupTarget.app;
-                updateOomAdjLocked(proc, OomAdjuster.OOM_ADJ_REASON_NONE);
+                updateOomAdjLocked(proc, OOM_ADJ_REASON_BACKUP);
                 proc.setInFullBackup(false);
                 proc.mProfile.clearHostingComponentType(HOSTING_COMPONENT_TYPE_BACKUP);
 
@@ -13923,7 +13928,7 @@
                 // If we actually concluded any broadcasts, we might now be able
                 // to trim the recipients' apps from our working set
                 if (doTrim) {
-                    trimApplicationsLocked(false, OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER);
+                    trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
                     return;
                 }
             }
@@ -15185,7 +15190,7 @@
                 queue.finishReceiverLocked(callerApp, resultCode,
                         resultData, resultExtras, resultAbort, true);
                 // updateOomAdjLocked() will be done here
-                trimApplicationsLocked(false, OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER);
+                trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
             }
 
         } finally {
@@ -16130,7 +16135,7 @@
             item.foregroundServiceTypes = fgServiceTypes;
         }
         if (oomAdj) {
-            updateOomAdjLocked(proc, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+            updateOomAdjLocked(proc, OOM_ADJ_REASON_UI_VISIBILITY);
         }
     }
 
@@ -16196,7 +16201,7 @@
      * {@link #enqueueOomAdjTargetLocked}.
      */
     @GuardedBy("this")
-    void updateOomAdjPendingTargetsLocked(@OomAdjuster.OomAdjReason int oomAdjReason) {
+    void updateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
         mOomAdjuster.updateOomAdjPendingTargetsLocked(oomAdjReason);
     }
 
@@ -16215,7 +16220,7 @@
     }
 
     @GuardedBy("this")
-    final void updateOomAdjLocked(@OomAdjuster.OomAdjReason int oomAdjReason) {
+    final void updateOomAdjLocked(@OomAdjReason int oomAdjReason) {
         mOomAdjuster.updateOomAdjLocked(oomAdjReason);
     }
 
@@ -16227,8 +16232,7 @@
      * @return whether updateOomAdjLocked(app) was successful.
      */
     @GuardedBy("this")
-    final boolean updateOomAdjLocked(
-            ProcessRecord app, @OomAdjuster.OomAdjReason int oomAdjReason) {
+    final boolean updateOomAdjLocked(ProcessRecord app, @OomAdjReason int oomAdjReason) {
         return mOomAdjuster.updateOomAdjLocked(app, oomAdjReason);
     }
 
@@ -16461,16 +16465,14 @@
         mOomAdjuster.setUidTempAllowlistStateLSP(uid, onAllowlist);
     }
 
-    private void trimApplications(
-            boolean forceFullOomAdj, @OomAdjuster.OomAdjReason int oomAdjReason) {
+    private void trimApplications(boolean forceFullOomAdj, @OomAdjReason int oomAdjReason) {
         synchronized (this) {
             trimApplicationsLocked(forceFullOomAdj, oomAdjReason);
         }
     }
 
     @GuardedBy("this")
-    private void trimApplicationsLocked(
-            boolean forceFullOomAdj, @OomAdjuster.OomAdjReason int oomAdjReason) {
+    private void trimApplicationsLocked(boolean forceFullOomAdj, @OomAdjReason int oomAdjReason) {
         // First remove any unused application processes whose package
         // has been removed.
         boolean didSomething = false;
@@ -17442,7 +17444,7 @@
                 }
                 pr.mState.setHasOverlayUi(hasOverlayUi);
                 //Slog.i(TAG, "Setting hasOverlayUi=" + pr.hasOverlayUi + " for pid=" + pid);
-                updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+                updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
             }
         }
 
@@ -17577,7 +17579,7 @@
 
         @Override
         public void trimApplications() {
-            ActivityManagerService.this.trimApplications(true, OomAdjuster.OOM_ADJ_REASON_ACTIVITY);
+            ActivityManagerService.this.trimApplications(true, OOM_ADJ_REASON_ACTIVITY);
         }
 
         public void killProcessesForRemovedTask(ArrayList<Object> procsToKill) {
@@ -17626,9 +17628,9 @@
         }
 
         @Override
-        public void updateOomAdj() {
+        public void updateOomAdj(@OomAdjReason int oomAdjReason) {
             synchronized (ActivityManagerService.this) {
-                ActivityManagerService.this.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+                ActivityManagerService.this.updateOomAdjLocked(oomAdjReason);
             }
         }
 
@@ -18288,8 +18290,7 @@
             // sends to the activity. After this race issue between WM/ATMS and AMS is solved, this
             // workaround can be removed. (b/213288355)
             if (isNewPending) {
-                mOomAdjuster.mCachedAppOptimizer.unfreezeProcess(pid,
-                        OomAdjuster.OOM_ADJ_REASON_ACTIVITY);
+                mOomAdjuster.mCachedAppOptimizer.unfreezeProcess(pid, OOM_ADJ_REASON_ACTIVITY);
             }
             // We need to update the network rules for the app coming to the top state so that
             // it can access network when the device or the app is in a restricted state
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index e080a80..17a0d62 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -247,6 +247,10 @@
                     return runSendBroadcast(pw);
                 case "compact":
                     return runCompact(pw);
+                case "freeze":
+                    return runFreeze(pw);
+                case "unfreeze":
+                    return runUnfreeze(pw);
                 case "instrument":
                     getOutPrintWriter().println("Error: must be invoked through 'am instrument'.");
                     return -1;
@@ -1074,20 +1078,10 @@
         boolean isFullCompact = op.equals("full");
         boolean isSomeCompact = op.equals("some");
         if (isFullCompact || isSomeCompact) {
-            String processName = getNextArgRequired();
-            synchronized (mInternal.mProcLock) {
-                // Default to current user
-                int userId = mInterface.getCurrentUserId();
-                String userOpt = getNextOption();
-                if (userOpt != null && "--user".equals(userOpt)) {
-                    int inputUserId = UserHandle.parseUserArg(getNextArgRequired());
-                    if (inputUserId != UserHandle.USER_CURRENT) {
-                        userId = inputUserId;
-                    }
-                }
-                final int uid =
-                        mInternal.getPackageManagerInternal().getPackageUid(processName, 0, userId);
-                app = mInternal.getProcessRecordLocked(processName, uid);
+            app = getProcessFromShell();
+            if (app == null) {
+                getErrPrintWriter().println("Error: could not find process");
+                return -1;
             }
             pw.println("Process record found pid: " + app.mPid);
             if (isFullCompact) {
@@ -1143,6 +1137,93 @@
         return 0;
     }
 
+    @NeverCompile
+    int runFreeze(PrintWriter pw) throws RemoteException {
+        String freezerOpt = getNextOption();
+        boolean isSticky = false;
+        if (freezerOpt != null) {
+            isSticky = freezerOpt.equals("--sticky");
+        }
+        ProcessRecord app = getProcessFromShell();
+        if (app == null) {
+            getErrPrintWriter().println("Error: could not find process");
+            return -1;
+        }
+        pw.println("Freezing pid: " + app.mPid + " sticky=" + isSticky);
+        synchronized (mInternal) {
+            synchronized (mInternal.mProcLock) {
+                app.mOptRecord.setFreezeSticky(isSticky);
+                mInternal.mOomAdjuster.mCachedAppOptimizer.freezeAppAsyncInternalLSP(app, 0, true);
+            }
+        }
+        return 0;
+    }
+
+    @NeverCompile
+    int runUnfreeze(PrintWriter pw) throws RemoteException {
+        String freezerOpt = getNextOption();
+        boolean isSticky = false;
+        if (freezerOpt != null) {
+            isSticky = freezerOpt.equals("--sticky");
+        }
+        ProcessRecord app = getProcessFromShell();
+        if (app == null) {
+            getErrPrintWriter().println("Error: could not find process");
+            return -1;
+        }
+        pw.println("Unfreezing pid: " + app.mPid);
+        synchronized (mInternal) {
+            synchronized (mInternal.mProcLock) {
+                synchronized (mInternal.mOomAdjuster.mCachedAppOptimizer.mFreezerLock) {
+                    app.mOptRecord.setFreezeSticky(isSticky);
+                    mInternal.mOomAdjuster.mCachedAppOptimizer.unfreezeAppInternalLSP(app, 0,
+                            false);
+                }
+            }
+        }
+        return 0;
+    }
+
+    /**
+     * Parses from the shell the process name and user id if provided and provides the corresponding
+     * {@link ProcessRecord)} If no user is provided, it will fallback to current user.
+     * Example usage: {@code <processname> --user current} or {@code <processname>}
+     * @return process record of process, null if none found.
+     * @throws RemoteException
+     */
+    @NeverCompile
+    ProcessRecord getProcessFromShell() throws RemoteException {
+        ProcessRecord app;
+        String processName = getNextArgRequired();
+        synchronized (mInternal.mProcLock) {
+            // Default to current user
+            int userId = getUserIdFromShellOrFallback();
+            final int uid =
+                    mInternal.getPackageManagerInternal().getPackageUid(processName, 0, userId);
+            app = mInternal.getProcessRecordLocked(processName, uid);
+        }
+        return app;
+    }
+
+    /**
+     * @return User id from command line provided in the form of
+     *  {@code --user <userid|current|all>} and if the argument is not found it will fallback
+     *  to current user.
+     * @throws RemoteException
+     */
+    @NeverCompile
+    int getUserIdFromShellOrFallback() throws RemoteException {
+        int userId = mInterface.getCurrentUserId();
+        String userOpt = getNextOption();
+        if (userOpt != null && "--user".equals(userOpt)) {
+            int inputUserId = UserHandle.parseUserArg(getNextArgRequired());
+            if (inputUserId != UserHandle.USER_CURRENT) {
+                userId = inputUserId;
+            }
+        }
+        return userId;
+    }
+
     int runDumpHeap(PrintWriter pw) throws RemoteException {
         final PrintWriter err = getErrPrintWriter();
         boolean managed = true;
@@ -4061,6 +4142,14 @@
             pw.println("      Perform a native compaction for process with <pid>.");
             pw.println("      some: execute file compaction.");
             pw.println("      full: execute anon + file compaction.");
+            pw.println("  freeze [--sticky] <processname> [--user <USER_ID>]");
+            pw.println("      Freeze a process.");
+            pw.println("        --sticky: persists the frozen state for the process lifetime or");
+            pw.println("                  until an unfreeze is triggered via shell");
+            pw.println("  unfreeze [--sticky] <processname> [--user <USER_ID>]");
+            pw.println("      Unfreeze a process.");
+            pw.println("        --sticky: persists the unfrozen state for the process lifetime or");
+            pw.println("                  until a freeze is triggered via shell");
             pw.println("  instrument [-r] [-e <NAME> <VALUE>] [-p <FILE>] [-w]");
             pw.println("          [--user <USER_ID> | current]");
             pw.println("          [--no-hidden-api-checks [--no-test-api-access]]");
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index c343ec2..061bcd7 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -555,6 +555,15 @@
         }
     }
 
+    void sendRecoverableCrashToAppExitInfo(
+            ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo) {
+        if (r == null || crashInfo == null
+                || !"Native crash".equals(crashInfo.exceptionClassName)) return;
+        synchronized (mService) {
+            mService.mProcessList.noteAppRecoverableCrash(r);
+        }
+    }
+
     /**
      * Bring up the "unexpected error" dialog box for a crashing app.
      * Deal with edge cases (intercepts from instrumented applications,
diff --git a/services/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java
index 4443636..4c0dd11 100644
--- a/services/core/java/com/android/server/am/AppExitInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppExitInfoTracker.java
@@ -308,6 +308,16 @@
         mKillHandler.obtainMessage(KillHandler.MSG_APP_KILL, raw).sendToTarget();
     }
 
+    void scheduleNoteAppRecoverableCrash(final ProcessRecord app) {
+        if (!mAppExitInfoLoaded.get() || app == null || app.info == null) return;
+
+        ApplicationExitInfo raw = obtainRawRecord(app, System.currentTimeMillis());
+        raw.setReason(ApplicationExitInfo.REASON_CRASH_NATIVE);
+        raw.setSubReason(ApplicationExitInfo.SUBREASON_UNKNOWN);
+        raw.setDescription("recoverable_crash");
+        mKillHandler.obtainMessage(KillHandler.MSG_APP_RECOVERABLE_CRASH, raw).sendToTarget();
+    }
+
     void scheduleNoteAppKill(final int pid, final int uid, final @Reason int reason,
             final @SubReason int subReason, final String msg) {
         if (!mAppExitInfoLoaded.get()) {
@@ -421,8 +431,24 @@
         scheduleLogToStatsdLocked(info, true);
     }
 
+    /**
+     * Make note when ActivityManagerService gets a recoverable native crash, as the process isn't
+     * being killed but the crash should still be added to AppExitInfo. Also, because we're not
+     * crashing, don't log out to statsd.
+     */
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    void handleNoteAppRecoverableCrashLocked(final ApplicationExitInfo raw) {
+        addExitInfoLocked(raw, /* recoverable */ true);
+    }
+
     @GuardedBy("mLock")
     private ApplicationExitInfo addExitInfoLocked(ApplicationExitInfo raw) {
+        return addExitInfoLocked(raw, /* recoverable */ false);
+    }
+
+    @GuardedBy("mLock")
+    private ApplicationExitInfo addExitInfoLocked(ApplicationExitInfo raw, boolean recoverable) {
         if (!mAppExitInfoLoaded.get()) {
             Slog.w(TAG, "Skipping saving the exit info due to ongoing loading from storage");
             return null;
@@ -438,7 +464,7 @@
             }
         }
         for (int i = 0; i < packages.length; i++) {
-            addExitInfoInnerLocked(packages[i], uid, info);
+            addExitInfoInnerLocked(packages[i], uid, info, recoverable);
         }
 
         schedulePersistProcessExitInfo(false);
@@ -845,7 +871,8 @@
     }
 
     @GuardedBy("mLock")
-    private void addExitInfoInnerLocked(String packageName, int uid, ApplicationExitInfo info) {
+    private void addExitInfoInnerLocked(String packageName, int uid, ApplicationExitInfo info,
+            boolean recoverable) {
         AppExitInfoContainer container = mData.get(packageName, uid);
         if (container == null) {
             container = new AppExitInfoContainer(mAppExitInfoHistoryListSize);
@@ -859,7 +886,11 @@
             }
             mData.put(packageName, uid, container);
         }
-        container.addExitInfoLocked(info);
+        if (recoverable) {
+            container.addRecoverableCrashLocked(info);
+        } else {
+            container.addExitInfoLocked(info);
+        }
     }
 
     @GuardedBy("mLock")
@@ -1284,38 +1315,40 @@
      * A container class of {@link android.app.ApplicationExitInfo}
      */
     final class AppExitInfoContainer {
-        private SparseArray<ApplicationExitInfo> mInfos; // index is pid
+        private SparseArray<ApplicationExitInfo> mInfos; // index is a pid
+        private SparseArray<ApplicationExitInfo> mRecoverableCrashes; // index is a pid
         private int mMaxCapacity;
         private int mUid; // Application uid, not isolated uid.
 
         AppExitInfoContainer(final int maxCapacity) {
             mInfos = new SparseArray<ApplicationExitInfo>();
+            mRecoverableCrashes = new SparseArray<ApplicationExitInfo>();
             mMaxCapacity = maxCapacity;
         }
 
         @GuardedBy("mLock")
-        void getExitInfoLocked(final int filterPid, final int maxNum,
-                ArrayList<ApplicationExitInfo> results) {
+        void getInfosLocked(SparseArray<ApplicationExitInfo> map, final int filterPid,
+                final int maxNum, ArrayList<ApplicationExitInfo> results) {
             if (filterPid > 0) {
-                ApplicationExitInfo r = mInfos.get(filterPid);
+                ApplicationExitInfo r = map.get(filterPid);
                 if (r != null) {
                     results.add(r);
                 }
             } else {
-                final int numRep = mInfos.size();
+                final int numRep = map.size();
                 if (maxNum <= 0 || numRep <= maxNum) {
                     // Return all records.
                     for (int i = 0; i < numRep; i++) {
-                        results.add(mInfos.valueAt(i));
+                        results.add(map.valueAt(i));
                     }
                     Collections.sort(results,
                             (a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp()));
                 } else {
                     if (maxNum == 1) {
                         // Most of the caller might be only interested with the most recent one
-                        ApplicationExitInfo r = mInfos.valueAt(0);
+                        ApplicationExitInfo r = map.valueAt(0);
                         for (int i = 1; i < numRep; i++) {
-                            ApplicationExitInfo t = mInfos.valueAt(i);
+                            ApplicationExitInfo t = map.valueAt(i);
                             if (r.getTimestamp() < t.getTimestamp()) {
                                 r = t;
                             }
@@ -1326,7 +1359,7 @@
                         ArrayList<ApplicationExitInfo> list = mTmpInfoList2;
                         list.clear();
                         for (int i = 0; i < numRep; i++) {
-                            list.add(mInfos.valueAt(i));
+                            list.add(map.valueAt(i));
                         }
                         Collections.sort(list,
                                 (a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp()));
@@ -1340,24 +1373,30 @@
         }
 
         @GuardedBy("mLock")
-        void addExitInfoLocked(ApplicationExitInfo info) {
+        void getExitInfoLocked(final int filterPid, final int maxNum,
+                ArrayList<ApplicationExitInfo> results) {
+            getInfosLocked(mInfos, filterPid, maxNum, results);
+        }
+
+        @GuardedBy("mLock")
+        void addInfoLocked(SparseArray<ApplicationExitInfo> map, ApplicationExitInfo info) {
             int size;
-            if ((size = mInfos.size()) >= mMaxCapacity) {
+            if ((size = map.size()) >= mMaxCapacity) {
                 int oldestIndex = -1;
                 long oldestTimeStamp = Long.MAX_VALUE;
                 for (int i = 0; i < size; i++) {
-                    ApplicationExitInfo r = mInfos.valueAt(i);
+                    ApplicationExitInfo r = map.valueAt(i);
                     if (r.getTimestamp() < oldestTimeStamp) {
                         oldestTimeStamp = r.getTimestamp();
                         oldestIndex = i;
                     }
                 }
                 if (oldestIndex >= 0) {
-                    final File traceFile = mInfos.valueAt(oldestIndex).getTraceFile();
+                    final File traceFile = map.valueAt(oldestIndex).getTraceFile();
                     if (traceFile != null) {
                         traceFile.delete();
                     }
-                    mInfos.removeAt(oldestIndex);
+                    map.removeAt(oldestIndex);
                 }
             }
             // Claim the state information if there is any
@@ -1367,7 +1406,17 @@
                     mActiveAppStateSummary, uid, pid));
             info.setTraceFile(findAndRemoveFromSparse2dArray(mActiveAppTraces, uid, pid));
             info.setAppTraceRetriever(mAppTraceRetriever);
-            mInfos.append(pid, info);
+            map.append(pid, info);
+        }
+
+        @GuardedBy("mLock")
+        void addExitInfoLocked(ApplicationExitInfo info) {
+            addInfoLocked(mInfos, info);
+        }
+
+        @GuardedBy("mLock")
+        void addRecoverableCrashLocked(ApplicationExitInfo info) {
+            addInfoLocked(mRecoverableCrashes, info);
         }
 
         @GuardedBy("mLock")
@@ -1382,9 +1431,9 @@
         }
 
         @GuardedBy("mLock")
-        void destroyLocked() {
-            for (int i = mInfos.size() - 1; i >= 0; i--) {
-                ApplicationExitInfo ai = mInfos.valueAt(i);
+        void destroyLocked(SparseArray<ApplicationExitInfo> map) {
+            for (int i = map.size() - 1; i >= 0; i--) {
+                ApplicationExitInfo ai = map.valueAt(i);
                 final File traceFile = ai.getTraceFile();
                 if (traceFile != null) {
                     traceFile.delete();
@@ -1395,24 +1444,37 @@
         }
 
         @GuardedBy("mLock")
+        void destroyLocked() {
+            destroyLocked(mInfos);
+            destroyLocked(mRecoverableCrashes);
+        }
+
+        @GuardedBy("mLock")
         void forEachRecordLocked(final BiFunction<Integer, ApplicationExitInfo, Integer> callback) {
-            if (callback != null) {
-                for (int i = mInfos.size() - 1; i >= 0; i--) {
-                    switch (callback.apply(mInfos.keyAt(i), mInfos.valueAt(i))) {
-                        case FOREACH_ACTION_REMOVE_ITEM:
-                            final File traceFile = mInfos.valueAt(i).getTraceFile();
-                            if (traceFile != null) {
-                                traceFile.delete();
-                            }
-                            mInfos.removeAt(i);
-                            break;
-                        case FOREACH_ACTION_STOP_ITERATION:
-                            i = 0;
-                            break;
-                        case FOREACH_ACTION_NONE:
-                        default:
-                            break;
-                    }
+            if (callback == null) return;
+            for (int i = mInfos.size() - 1; i >= 0; i--) {
+                switch (callback.apply(mInfos.keyAt(i), mInfos.valueAt(i))) {
+                    case FOREACH_ACTION_STOP_ITERATION: return;
+                    case FOREACH_ACTION_REMOVE_ITEM:
+                        final File traceFile = mInfos.valueAt(i).getTraceFile();
+                        if (traceFile != null) {
+                            traceFile.delete();
+                        }
+                        mInfos.removeAt(i);
+                        break;
+                }
+            }
+            for (int i = mRecoverableCrashes.size() - 1; i >= 0; i--) {
+                switch (callback.apply(
+                        mRecoverableCrashes.keyAt(i), mRecoverableCrashes.valueAt(i))) {
+                    case FOREACH_ACTION_STOP_ITERATION: return;
+                    case FOREACH_ACTION_REMOVE_ITEM:
+                        final File traceFile = mRecoverableCrashes.valueAt(i).getTraceFile();
+                        if (traceFile != null) {
+                            traceFile.delete();
+                        }
+                        mRecoverableCrashes.removeAt(i);
+                        break;
                 }
             }
         }
@@ -1423,6 +1485,9 @@
             for (int i = mInfos.size() - 1; i >= 0; i--) {
                 list.add(mInfos.valueAt(i));
             }
+            for (int i = mRecoverableCrashes.size() - 1; i >= 0; i--) {
+                list.add(mRecoverableCrashes.valueAt(i));
+            }
             Collections.sort(list, (a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp()));
             int size = list.size();
             for (int i = 0; i < size; i++) {
@@ -1434,10 +1499,13 @@
         void writeToProto(ProtoOutputStream proto, long fieldId) {
             long token = proto.start(fieldId);
             proto.write(AppsExitInfoProto.Package.User.UID, mUid);
-            int size = mInfos.size();
-            for (int i = 0; i < size; i++) {
+            for (int i = 0; i < mInfos.size(); i++) {
                 mInfos.valueAt(i).writeToProto(proto, AppsExitInfoProto.Package.User.APP_EXIT_INFO);
             }
+            for (int i = 0; i < mRecoverableCrashes.size(); i++) {
+                mRecoverableCrashes.valueAt(i).writeToProto(
+                        proto, AppsExitInfoProto.Package.User.APP_RECOVERABLE_CRASH);
+            }
             proto.end(token);
         }
 
@@ -1448,14 +1516,23 @@
                     next != ProtoInputStream.NO_MORE_FIELDS;
                     next = proto.nextField()) {
                 switch (next) {
-                    case (int) AppsExitInfoProto.Package.User.UID:
+                    case (int) AppsExitInfoProto.Package.User.UID: {
                         mUid = proto.readInt(AppsExitInfoProto.Package.User.UID);
                         break;
-                    case (int) AppsExitInfoProto.Package.User.APP_EXIT_INFO:
+                    }
+                    case (int) AppsExitInfoProto.Package.User.APP_EXIT_INFO: {
                         ApplicationExitInfo info = new ApplicationExitInfo();
                         info.readFromProto(proto, AppsExitInfoProto.Package.User.APP_EXIT_INFO);
                         mInfos.put(info.getPid(), info);
                         break;
+                    }
+                    case (int) AppsExitInfoProto.Package.User.APP_RECOVERABLE_CRASH: {
+                        ApplicationExitInfo info = new ApplicationExitInfo();
+                        info.readFromProto(
+                                proto, AppsExitInfoProto.Package.User.APP_RECOVERABLE_CRASH);
+                        mRecoverableCrashes.put(info.getPid(), info);
+                        break;
+                    }
                 }
             }
             proto.end(token);
@@ -1472,6 +1549,11 @@
                     list.add(mInfos.valueAt(i));
                 }
             }
+            for (int i = mRecoverableCrashes.size() - 1; i >= 0; i--) {
+                if (filterPid == 0 || filterPid == mRecoverableCrashes.keyAt(i)) {
+                    list.add(mRecoverableCrashes.valueAt(i));
+                }
+            }
             return list;
         }
     }
@@ -1610,6 +1692,7 @@
         static final int MSG_PROC_DIED = 4103;
         static final int MSG_APP_KILL = 4104;
         static final int MSG_STATSD_LOG = 4105;
+        static final int MSG_APP_RECOVERABLE_CRASH = 4106;
 
         KillHandler(Looper looper) {
             super(looper, null, true);
@@ -1648,6 +1731,14 @@
                     }
                 }
                 break;
+                case MSG_APP_RECOVERABLE_CRASH: {
+                    ApplicationExitInfo raw = (ApplicationExitInfo) msg.obj;
+                    synchronized (mLock) {
+                        handleNoteAppRecoverableCrashLocked(raw);
+                    }
+                    recycleRawRecord(raw);
+                }
+                break;
                 default:
                     super.handleMessage(msg);
             }
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index ed297d0..0744f75 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -108,8 +108,8 @@
 import com.android.server.power.stats.BatteryStatsImpl;
 import com.android.server.power.stats.BatteryUsageStatsProvider;
 import com.android.server.power.stats.BatteryUsageStatsStore;
-import com.android.server.power.stats.CpuWakeupStats;
 import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
+import com.android.server.power.stats.wakeups.CpuWakeupStats;
 
 import java.io.File;
 import java.io.FileDescriptor;
@@ -515,13 +515,11 @@
         @Override
         public void noteCpuWakingActivity(int subsystem, long elapsedMillis, int... uids) {
             Objects.requireNonNull(uids);
-            mCpuWakeupStats.noteWakingActivity(subsystem, elapsedMillis, uids);
+            mHandler.post(() -> mCpuWakeupStats.noteWakingActivity(subsystem, elapsedMillis, uids));
         }
-
         @Override
         public void noteWakingSoundTrigger(long elapsedMillis, int uid) {
-            // TODO(b/267717665): Pipe to noteCpuWakingActivity once SoundTrigger starts using this.
-            Slog.w(TAG, "Sound trigger event dispatched to uid " + uid);
+            noteCpuWakingActivity(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER, elapsedMillis, uid);
         }
     }
 
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index bfc8251..0767218 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -18,9 +18,9 @@
 
 import static com.android.internal.util.Preconditions.checkState;
 import static com.android.server.am.BroadcastRecord.deliveryStateToString;
-import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal;
 import static com.android.server.am.BroadcastRecord.isReceiverEquals;
 
+import android.annotation.CheckResult;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -188,6 +188,12 @@
     private @Reason int mRunnableAtReason = REASON_EMPTY;
     private boolean mRunnableAtInvalidated;
 
+    /**
+     * Last state applied by {@link #updateDeferredStates}, used to quickly
+     * determine if a state transition is occurring.
+     */
+    private boolean mLastDeferredStates;
+
     private boolean mUidCached;
     private boolean mProcessInstrumented;
     private boolean mProcessPersistent;
@@ -236,7 +242,15 @@
      */
     @Nullable
     public BroadcastRecord enqueueOrReplaceBroadcast(@NonNull BroadcastRecord record,
-            int recordIndex, boolean wouldBeSkipped) {
+            int recordIndex, boolean wouldBeSkipped,
+            @NonNull BroadcastConsumer deferredStatesApplyConsumer) {
+        // When updateDeferredStates() has already applied a deferred state to
+        // all pending items, apply to this new broadcast too
+        if (mLastDeferredStates && record.deferUntilActive
+                && (record.getDeliveryState(recordIndex) == BroadcastRecord.DELIVERY_PENDING)) {
+            deferredStatesApplyConsumer.accept(record, recordIndex);
+        }
+
         if (record.isReplacePending()) {
             final BroadcastRecord replacedBroadcastRecord = replaceBroadcast(record, recordIndex,
                     wouldBeSkipped);
@@ -341,7 +355,12 @@
      * Predicates that choose to remove a broadcast <em>must</em> finish
      * delivery of the matched broadcast, to ensure that situations like ordered
      * broadcasts are handled consistently.
+     *
+     * @return if this operation may have changed internal state, indicating
+     *         that the caller is responsible for invoking
+     *         {@link BroadcastQueueModernImpl#updateRunnableList}
      */
+    @CheckResult
     public boolean forEachMatchingBroadcast(@NonNull BroadcastPredicate predicate,
             @NonNull BroadcastConsumer consumer, boolean andRemove) {
         boolean didSomething = false;
@@ -354,6 +373,7 @@
         return didSomething;
     }
 
+    @CheckResult
     private boolean forEachMatchingBroadcastInQueue(@NonNull ArrayDeque<SomeArgs> queue,
             @NonNull BroadcastPredicate predicate, @NonNull BroadcastConsumer consumer,
             boolean andRemove) {
@@ -370,6 +390,10 @@
                     args.recycle();
                     it.remove();
                     onBroadcastDequeued(record, recordIndex, recordWouldBeSkipped);
+                } else {
+                    // Even if we're leaving broadcast in queue, it may have
+                    // been mutated in such a way to change our runnable time
+                    invalidateRunnableAt();
                 }
                 didSomething = true;
             }
@@ -381,32 +405,44 @@
 
     /**
      * Update the actively running "warm" process for this process.
+     *
+     * @return if this operation may have changed internal state, indicating
+     *         that the caller is responsible for invoking
+     *         {@link BroadcastQueueModernImpl#updateRunnableList}
      */
-    public void setProcessAndUidCached(@Nullable ProcessRecord app, boolean uidCached) {
+    @CheckResult
+    public boolean setProcessAndUidCached(@Nullable ProcessRecord app, boolean uidCached) {
         this.app = app;
-        if (app != null) {
-            setUidCached(uidCached);
-            setProcessInstrumented(app.getActiveInstrumentation() != null);
-            setProcessPersistent(app.isPersistent());
-        } else {
-            setUidCached(uidCached);
-            setProcessInstrumented(false);
-            setProcessPersistent(false);
-        }
 
         // Since we may have just changed our PID, invalidate cached strings
         mCachedToString = null;
         mCachedToShortString = null;
+
+        boolean didSomething = false;
+        if (app != null) {
+            didSomething |= setUidCached(uidCached);
+            didSomething |= setProcessInstrumented(app.getActiveInstrumentation() != null);
+            didSomething |= setProcessPersistent(app.isPersistent());
+        } else {
+            didSomething |= setUidCached(uidCached);
+            didSomething |= setProcessInstrumented(false);
+            didSomething |= setProcessPersistent(false);
+        }
+        return didSomething;
     }
 
     /**
      * Update if this process is in the "cached" state, typically signaling that
      * broadcast dispatch should be paused or delayed.
      */
-    private void setUidCached(boolean uidCached) {
+    @CheckResult
+    private boolean setUidCached(boolean uidCached) {
         if (mUidCached != uidCached) {
             mUidCached = uidCached;
             invalidateRunnableAt();
+            return true;
+        } else {
+            return false;
         }
     }
 
@@ -415,10 +451,14 @@
      * signaling that broadcast dispatch should bypass all pauses or delays, to
      * avoid holding up test suites.
      */
-    private void setProcessInstrumented(boolean instrumented) {
+    @CheckResult
+    private boolean setProcessInstrumented(boolean instrumented) {
         if (mProcessInstrumented != instrumented) {
             mProcessInstrumented = instrumented;
             invalidateRunnableAt();
+            return true;
+        } else {
+            return false;
         }
     }
 
@@ -426,10 +466,14 @@
      * Update if this process is in the "persistent" state, which signals broadcast dispatch should
      * bypass all pauses or delays to prevent the system from becoming out of sync with itself.
      */
-    private void setProcessPersistent(boolean persistent) {
+    @CheckResult
+    private boolean setProcessPersistent(boolean persistent) {
         if (mProcessPersistent != persistent) {
             mProcessPersistent = persistent;
             invalidateRunnableAt();
+            return true;
+        } else {
+            return false;
         }
     }
 
@@ -649,8 +693,20 @@
         return mActive != null;
     }
 
-    void forceDelayBroadcastDelivery(long delayedDurationMs) {
-        mForcedDelayedDurationMs = delayedDurationMs;
+    /**
+     * @return if this operation may have changed internal state, indicating
+     *         that the caller is responsible for invoking
+     *         {@link BroadcastQueueModernImpl#updateRunnableList}
+     */
+    @CheckResult
+    boolean forceDelayBroadcastDelivery(long delayedDurationMs) {
+        if (mForcedDelayedDurationMs != delayedDurationMs) {
+            mForcedDelayedDurationMs = delayedDurationMs;
+            invalidateRunnableAt();
+            return true;
+        } else {
+            return false;
+        }
     }
 
     /**
@@ -709,7 +765,7 @@
                 || consecutiveHighPriorityCount >= maxHighPriorityDispatchLimit);
         final boolean isLPQueueEligible = shouldConsiderLPQueue
                 && nextLPRecord.enqueueTime <= nextHPRecord.enqueueTime
-                && !blockedOnOrderedDispatch(nextLPRecord, nextLPRecordIndex);
+                && !nextLPRecord.isBlocked(nextLPRecordIndex);
         return isLPQueueEligible ? lowPriorityQueue : highPriorityQueue;
     }
 
@@ -722,10 +778,21 @@
      * broadcasts would be prioritized for dispatching, even if there are urgent broadcasts
      * waiting. This is typically used in case there are callers waiting for "barrier" to be
      * reached.
+     *
+     * @return if this operation may have changed internal state, indicating
+     *         that the caller is responsible for invoking
+     *         {@link BroadcastQueueModernImpl#updateRunnableList}
      */
+    @CheckResult
     @VisibleForTesting
-    void setPrioritizeEarliest(boolean prioritizeEarliest) {
-        mPrioritizeEarliest = prioritizeEarliest;
+    boolean setPrioritizeEarliest(boolean prioritizeEarliest) {
+        if (mPrioritizeEarliest != prioritizeEarliest) {
+            mPrioritizeEarliest = prioritizeEarliest;
+            invalidateRunnableAt();
+            return true;
+        } else {
+            return false;
+        }
     }
 
     /**
@@ -912,39 +979,20 @@
         }
     }
 
-    private boolean blockedOnOrderedDispatch(BroadcastRecord r, int index) {
-        final int blockedUntilTerminalCount = r.blockedUntilTerminalCount[index];
-
-        int existingDeferredCount = 0;
-        if (r.deferUntilActive) {
-            for (int i = 0; i < index; i++) {
-                if (r.deferredUntilActive[i]) existingDeferredCount++;
-            }
-        }
-
-        // We might be blocked waiting for other receivers to finish,
-        // typically for an ordered broadcast or priority traunches
-        if ((r.terminalCount + existingDeferredCount) < blockedUntilTerminalCount
-                && !isDeliveryStateTerminal(r.getDeliveryState(index))) {
-            return true;
-        }
-        return false;
-    }
-
     /**
-     * Update {@link #getRunnableAt()} if it's currently invalidated.
+     * Update {@link #getRunnableAt()}, when needed.
      */
-    private void updateRunnableAt() {
-        final SomeArgs next = peekNextBroadcast();
+    void updateRunnableAt() {
+        if (!mRunnableAtInvalidated) return;
         mRunnableAtInvalidated = false;
+
+        final SomeArgs next = peekNextBroadcast();
         if (next != null) {
             final BroadcastRecord r = (BroadcastRecord) next.arg1;
             final int index = next.argi1;
             final long runnableAt = r.enqueueTime;
 
-            // If we're specifically queued behind other ordered dispatch activity,
-            // we aren't runnable yet
-            if (blockedOnOrderedDispatch(r, index)) {
+            if (r.isBlocked(index)) {
                 mRunnableAt = Long.MAX_VALUE;
                 mRunnableAtReason = REASON_BLOCKED;
                 return;
@@ -1047,10 +1095,44 @@
     }
 
     /**
+     * Update {@link BroadcastRecord.DELIVERY_DEFERRED} states of all our
+     * pending broadcasts, when needed.
+     */
+    void updateDeferredStates(@NonNull BroadcastConsumer applyConsumer,
+            @NonNull BroadcastConsumer clearConsumer) {
+        // When all we have pending is deferred broadcasts, and we're cached,
+        // then we want everything to be marked deferred
+        final boolean wantDeferredStates = (mCountDeferred > 0)
+                && (mCountDeferred == mCountEnqueued) && mUidCached;
+
+        if (mLastDeferredStates != wantDeferredStates) {
+            mLastDeferredStates = wantDeferredStates;
+            if (wantDeferredStates) {
+                forEachMatchingBroadcast((r, i) -> {
+                    return r.deferUntilActive
+                            && (r.getDeliveryState(i) == BroadcastRecord.DELIVERY_PENDING);
+                }, applyConsumer, false);
+            } else {
+                forEachMatchingBroadcast((r, i) -> {
+                    return r.deferUntilActive
+                            && (r.getDeliveryState(i) == BroadcastRecord.DELIVERY_DEFERRED);
+                }, clearConsumer, false);
+            }
+        }
+    }
+
+    /**
      * Check overall health, confirming things are in a reasonable state and
      * that we're not wedged.
      */
     public void assertHealthLocked() {
+        // If we're not actively running, we should be sorted into the runnable
+        // list, and if we're invalidated then someone likely forgot to invoke
+        // updateRunnableList() to re-sort us into place
+        if (!isActive()) {
+            checkState(!mRunnableAtInvalidated, "mRunnableAtInvalidated");
+        }
+
         assertHealthLocked(mPending);
         assertHealthLocked(mPendingUrgent);
         assertHealthLocked(mPendingOffload);
@@ -1153,19 +1235,30 @@
         return mCachedToShortString;
     }
 
+    public String describeStateLocked() {
+        return describeStateLocked(SystemClock.uptimeMillis());
+    }
+
+    public String describeStateLocked(@UptimeMillisLong long now) {
+        final StringBuilder sb = new StringBuilder();
+        if (isRunnable()) {
+            sb.append("runnable at ");
+            TimeUtils.formatDuration(getRunnableAt(), now, sb);
+        } else {
+            sb.append("not runnable");
+        }
+        sb.append(" because ");
+        sb.append(reasonToString(mRunnableAtReason));
+        return sb.toString();
+    }
+
     @NeverCompile
     public void dumpLocked(@UptimeMillisLong long now, @NonNull IndentingPrintWriter pw) {
         if ((mActive == null) && isEmpty()) return;
 
         pw.print(toShortString());
-        if (isRunnable()) {
-            pw.print(" runnable at ");
-            TimeUtils.formatDuration(getRunnableAt(), now, pw);
-        } else {
-            pw.print(" not runnable");
-        }
-        pw.print(" because ");
-        pw.print(reasonToString(mRunnableAtReason));
+        pw.print(" ");
+        pw.print(describeStateLocked(now));
         pw.println();
 
         pw.increaseIndent();
@@ -1262,12 +1355,12 @@
             pw.print(info.activityInfo.name);
         }
         pw.println();
-        final int blockedUntilTerminalCount = record.blockedUntilTerminalCount[recordIndex];
-        if (blockedUntilTerminalCount != -1) {
+        final int blockedUntilBeyondCount = record.blockedUntilBeyondCount[recordIndex];
+        if (blockedUntilBeyondCount != -1) {
             pw.print("    blocked until ");
-            pw.print(blockedUntilTerminalCount);
+            pw.print(blockedUntilBeyondCount);
             pw.print(", currently at ");
-            pw.print(record.terminalCount);
+            pw.print(record.beyondCount);
             pw.print(" of ");
             pw.println(record.receivers.size());
         }
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index bd36c3f..5a4d315 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -17,6 +17,7 @@
 package com.android.server.am;
 
 import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
 import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
 import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
 import static android.text.TextUtils.formatSimple;
@@ -37,7 +38,6 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
-import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index a4bdf61..8735f8a 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -16,6 +16,7 @@
 
 package com.android.server.am;
 
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
 import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
 import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
 
@@ -38,7 +39,6 @@
 import static com.android.server.am.BroadcastRecord.getReceiverProcessName;
 import static com.android.server.am.BroadcastRecord.getReceiverUid;
 import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal;
-import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -327,6 +327,12 @@
             return;
         }
 
+        // To place ourselves correctly in the runnable list, we may need to
+        // update internals that may have been invalidated; we wait until now at
+        // the last possible moment to avoid duplicated work
+        queue.updateDeferredStates(mBroadcastConsumerDeferApply, mBroadcastConsumerDeferClear);
+        queue.updateRunnableAt();
+
         final boolean wantQueue = queue.isRunnable();
         final boolean inQueue = (queue == mRunnableHead) || (queue.runnableAtPrev != null)
                 || (queue.runnableAtNext != null);
@@ -352,8 +358,6 @@
         // If app isn't running, and there's nothing in the queue, clean up
         if (queue.isEmpty() && !queue.isActive() && !queue.isProcessWarm()) {
             removeProcessQueue(queue.processName, queue.uid);
-        } else {
-            updateQueueDeferred(queue);
         }
     }
 
@@ -619,14 +623,10 @@
             }
             enqueuedBroadcast = true;
             final BroadcastRecord replacedBroadcast = queue.enqueueOrReplaceBroadcast(
-                    r, i, wouldBeSkipped);
+                    r, i, wouldBeSkipped, mBroadcastConsumerDeferApply);
             if (replacedBroadcast != null) {
                 replacedBroadcasts.add(replacedBroadcast);
             }
-            if (r.isDeferUntilActive() && queue.isDeferredUntilActive()) {
-                setDeliveryState(queue, null, r, i, receiver, BroadcastRecord.DELIVERY_DEFERRED,
-                        "deferred at enqueue time");
-            }
             updateRunnableList(queue);
             enqueueUpdateRunningList();
         }
@@ -1008,6 +1008,7 @@
         }
 
         final BroadcastRecord r = queue.getActive();
+        final int index = queue.getActiveIndex();
         if (r.ordered) {
             r.resultCode = resultCode;
             r.resultData = resultData;
@@ -1015,18 +1016,24 @@
             if (!r.isNoAbort()) {
                 r.resultAbort = resultAbort;
             }
+        }
 
-            // When the caller aborted an ordered broadcast, we mark all
-            // remaining receivers as skipped
-            if (r.resultAbort) {
-                for (int i = r.terminalCount + 1; i < r.receivers.size(); i++) {
-                    setDeliveryState(null, null, r, i, r.receivers.get(i),
-                            BroadcastRecord.DELIVERY_SKIPPED, "resultAbort");
-                }
+        // To ensure that "beyond" high-water marks are updated in a monotonic
+        // way, we finish this receiver before possibly skipping any remaining
+        // aborted receivers
+        final boolean res = finishReceiverActiveLocked(queue,
+                BroadcastRecord.DELIVERY_DELIVERED, "remote app");
+
+        // When the caller aborted an ordered broadcast, we mark all
+        // remaining receivers as skipped
+        if (r.resultAbort) {
+            for (int i = index + 1; i < r.receivers.size(); i++) {
+                setDeliveryState(null, null, r, i, r.receivers.get(i),
+                        BroadcastRecord.DELIVERY_SKIPPED, "resultAbort");
             }
         }
 
-        return finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_DELIVERED, "remote app");
+        return res;
     }
 
     /**
@@ -1108,21 +1115,10 @@
             @NonNull Object receiver, @DeliveryState int newDeliveryState,
             @NonNull String reason) {
         final int cookie = traceBegin("setDeliveryState");
-        final int oldDeliveryState = getDeliveryState(r, index);
-        boolean checkFinished = false;
 
-        // Only apply state when we haven't already reached a terminal state;
-        // this is how we ignore racing timeout messages
-        if (!isDeliveryStateTerminal(oldDeliveryState)) {
-            r.setDeliveryState(index, newDeliveryState, reason);
-            if (oldDeliveryState == BroadcastRecord.DELIVERY_DEFERRED) {
-                r.deferredCount--;
-            } else if (newDeliveryState == BroadcastRecord.DELIVERY_DEFERRED) {
-                // If we're deferring a broadcast, maybe that's enough to unblock the final callback
-                r.deferredCount++;
-                checkFinished = true;
-            }
-        }
+        // Remember the old state and apply the new state
+        final int oldDeliveryState = getDeliveryState(r, index);
+        final boolean beyondCountChanged = r.setDeliveryState(index, newDeliveryState, reason);
 
         // Emit any relevant tracing results when we're changing the delivery
         // state as part of running from a queue
@@ -1147,15 +1143,13 @@
                         + deliveryStateToString(newDeliveryState) + " because " + reason);
             }
 
-            r.terminalCount++;
             notifyFinishReceiver(queue, app, r, index, receiver);
-            checkFinished = true;
         }
-        // When entire ordered broadcast finished, deliver final result
-        if (checkFinished) {
-            final boolean recordFinished =
-                    ((r.terminalCount + r.deferredCount) == r.receivers.size());
-            if (recordFinished) {
+
+        // When we've reached a new high-water mark, we might be in a position
+        // to unblock other receivers or the final resultTo
+        if (beyondCountChanged) {
+            if (r.beyondCount == r.receivers.size()) {
                 scheduleResultTo(r);
             }
 
@@ -1255,14 +1249,14 @@
         r.resultExtras = null;
     };
 
-    private final BroadcastConsumer mBroadcastConsumerDefer = (r, i) -> {
+    private final BroadcastConsumer mBroadcastConsumerDeferApply = (r, i) -> {
         setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_DEFERRED,
-                "mBroadcastConsumerDefer");
+                "mBroadcastConsumerDeferApply");
     };
 
-    private final BroadcastConsumer mBroadcastConsumerUndoDefer = (r, i) -> {
+    private final BroadcastConsumer mBroadcastConsumerDeferClear = (r, i) -> {
         setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_PENDING,
-                "mBroadcastConsumerUndoDefer");
+                "mBroadcastConsumerDeferClear");
     };
 
     /**
@@ -1278,7 +1272,8 @@
                     final long now = SystemClock.uptimeMillis();
                     if (now > mLastTestFailureTime + DateUtils.SECOND_IN_MILLIS) {
                         mLastTestFailureTime = now;
-                        pw.println("Test " + label + " failed due to " + leaf.toShortString());
+                        pw.println("Test " + label + " failed due to " + leaf.toShortString() + " "
+                                + leaf.describeStateLocked());
                         pw.flush();
                     }
                     return false;
@@ -1315,34 +1310,25 @@
         return didSomething;
     }
 
-    private void forEachMatchingQueue(
+    private boolean forEachMatchingQueue(
             @NonNull Predicate<BroadcastProcessQueue> queuePredicate,
             @NonNull Consumer<BroadcastProcessQueue> queueConsumer) {
+        boolean didSomething = false;
         for (int i = mProcessQueues.size() - 1; i >= 0; i--) {
             BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
             while (leaf != null) {
                 if (queuePredicate.test(leaf)) {
                     queueConsumer.accept(leaf);
                     updateRunnableList(leaf);
+                    didSomething = true;
                 }
                 leaf = leaf.processNameNext;
             }
         }
-    }
-
-    private void updateQueueDeferred(
-            @NonNull BroadcastProcessQueue leaf) {
-        if (leaf.isDeferredUntilActive()) {
-            leaf.forEachMatchingBroadcast((r, i) -> {
-                return r.deferUntilActive && (r.getDeliveryState(i)
-                        == BroadcastRecord.DELIVERY_PENDING);
-            }, mBroadcastConsumerDefer, false);
-        } else if (leaf.hasDeferredBroadcasts()) {
-            leaf.forEachMatchingBroadcast((r, i) -> {
-                return r.deferUntilActive && (r.getDeliveryState(i)
-                        == BroadcastRecord.DELIVERY_DEFERRED);
-            }, mBroadcastConsumerUndoDefer, false);
+        if (didSomething) {
+            enqueueUpdateRunningList();
         }
+        return didSomething;
     }
 
     @Override
@@ -1365,8 +1351,6 @@
                         // Update internal state by refreshing values previously
                         // read from any known running process
                         setQueueProcess(leaf, leaf.app);
-                        updateQueueDeferred(leaf);
-                        updateRunnableList(leaf);
                         leaf = leaf.processNameNext;
                     }
                     enqueueUpdateRunningList();
@@ -1535,19 +1519,31 @@
         }
     }
 
+    @SuppressWarnings("CheckResult")
     private void updateWarmProcess(@NonNull BroadcastProcessQueue queue) {
         if (!queue.isProcessWarm()) {
-            setQueueProcess(queue, mService.getProcessRecordLocked(queue.processName, queue.uid));
+            // This is a bit awkward; we're in the middle of traversing the
+            // runnable queue, so we can't reorder that list if the runnable
+            // time changes here. However, if this process was just found to be
+            // warm via this operation, we're going to immediately promote it to
+            // be running, and any side effect of this operation will then apply
+            // after it's finished and is returned to the runnable list.
+            queue.setProcessAndUidCached(
+                    mService.getProcessRecordLocked(queue.processName, queue.uid),
+                    mUidCached.get(queue.uid, false));
         }
     }
 
     /**
      * Update the {@link ProcessRecord} associated with the given
-     * {@link BroadcastProcessQueue}.
+     * {@link BroadcastProcessQueue}. Also updates any runnable status that
+     * might have changed as a side-effect.
      */
     private void setQueueProcess(@NonNull BroadcastProcessQueue queue,
             @Nullable ProcessRecord app) {
-        queue.setProcessAndUidCached(app, mUidCached.get(queue.uid, false));
+        if (queue.setProcessAndUidCached(app, mUidCached.get(queue.uid, false))) {
+            updateRunnableList(queue);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index c368290..64fe393 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -24,6 +24,7 @@
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_TARGET_T_ONLY;
 
+import android.annotation.CheckResult;
 import android.annotation.CurrentTimeMillisLong;
 import android.annotation.ElapsedRealtimeLong;
 import android.annotation.IntDef;
@@ -101,8 +102,7 @@
     final @NonNull List<Object> receivers;   // contains BroadcastFilter and ResolveInfo
     final @DeliveryState int[] delivery;   // delivery state of each receiver
     final @NonNull String[] deliveryReasons; // reasons for delivery state of each receiver
-    final boolean[] deferredUntilActive; // whether each receiver is infinitely deferred
-    final int[] blockedUntilTerminalCount; // blocked until count of each receiver
+    final int[] blockedUntilBeyondCount; // blocked until count of each receiver
     @Nullable ProcessRecord resultToApp; // who receives final result if non-null
     @Nullable IIntentReceiver resultTo; // who receives final result if non-null
     boolean deferred;
@@ -134,6 +134,7 @@
     int manifestSkipCount;  // number of manifest receivers skipped.
     int terminalCount;      // number of receivers in terminal state.
     int deferredCount;      // number of receivers in deferred state.
+    int beyondCount;        // high-water number of receivers we've moved beyond.
     @Nullable BroadcastQueue queue;   // the outbound queue handling this broadcast
 
     // Determines the privileges the app's process has in regard to background starts.
@@ -219,6 +220,23 @@
         }
     }
 
+    /**
+     * Return if the given delivery state is "beyond", which means that we've
+     * moved beyond this receiver, and future receivers are now unblocked.
+     */
+    static boolean isDeliveryStateBeyond(@DeliveryState int deliveryState) {
+        switch (deliveryState) {
+            case DELIVERY_DELIVERED:
+            case DELIVERY_SKIPPED:
+            case DELIVERY_TIMEOUT:
+            case DELIVERY_FAILURE:
+            case DELIVERY_DEFERRED:
+                return true;
+            default:
+                return false;
+        }
+    }
+
     ProcessRecord curApp;       // hosting application of current receiver.
     ComponentName curComponent; // the receiver class that is currently running.
     ActivityInfo curReceiver;   // the manifest receiver that is currently running.
@@ -356,7 +374,7 @@
                 TimeUtils.formatDuration(terminalTime[i] - scheduledTime[i], pw);
                 pw.print(' ');
             }
-            pw.print("("); pw.print(blockedUntilTerminalCount[i]); pw.print(") ");
+            pw.print("("); pw.print(blockedUntilBeyondCount[i]); pw.print(") ");
             pw.print("#"); pw.print(i); pw.print(": ");
             if (o instanceof BroadcastFilter) {
                 pw.println(o);
@@ -411,8 +429,7 @@
         urgent = calculateUrgent(_intent, _options);
         deferUntilActive = calculateDeferUntilActive(_callingUid,
                 _options, _resultTo, _serialized, urgent);
-        deferredUntilActive = new boolean[deferUntilActive ? delivery.length : 0];
-        blockedUntilTerminalCount = calculateBlockedUntilTerminalCount(receivers, _serialized);
+        blockedUntilBeyondCount = calculateBlockedUntilBeyondCount(receivers, _serialized);
         scheduledTime = new long[delivery.length];
         terminalTime = new long[delivery.length];
         resultToApp = _resultToApp;
@@ -423,7 +440,7 @@
         ordered = _serialized;
         sticky = _sticky;
         initialSticky = _initialSticky;
-        prioritized = isPrioritized(blockedUntilTerminalCount, _serialized);
+        prioritized = isPrioritized(blockedUntilBeyondCount, _serialized);
         userId = _userId;
         nextReceiver = 0;
         state = IDLE;
@@ -467,8 +484,7 @@
         delivery = from.delivery;
         deliveryReasons = from.deliveryReasons;
         deferUntilActive = from.deferUntilActive;
-        deferredUntilActive = from.deferredUntilActive;
-        blockedUntilTerminalCount = from.blockedUntilTerminalCount;
+        blockedUntilBeyondCount = from.blockedUntilBeyondCount;
         scheduledTime = from.scheduledTime;
         terminalTime = from.terminalTime;
         resultToApp = from.resultToApp;
@@ -627,32 +643,72 @@
     /**
      * Update the delivery state of the given {@link #receivers} index.
      * Automatically updates any time measurements related to state changes.
+     *
+     * @return if {@link #beyondCount} changed due to this state transition,
+     *         indicating that other events may be unblocked.
      */
-    void setDeliveryState(int index, @DeliveryState int deliveryState,
+    @CheckResult
+    boolean setDeliveryState(int index, @DeliveryState int newDeliveryState,
             @NonNull String reason) {
-        delivery[index] = deliveryState;
-        deliveryReasons[index] = reason;
-        if (deferUntilActive) deferredUntilActive[index] = false;
-        switch (deliveryState) {
+        final int oldDeliveryState = delivery[index];
+        if (isDeliveryStateTerminal(oldDeliveryState)
+                || newDeliveryState == oldDeliveryState) {
+            // We've already arrived in terminal or requested state, so leave
+            // any statistics and reasons intact from the first transition
+            return false;
+        }
+
+        switch (oldDeliveryState) {
+            case DELIVERY_DEFERRED:
+                deferredCount--;
+                break;
+        }
+        switch (newDeliveryState) {
+            case DELIVERY_SCHEDULED:
+                scheduledTime[index] = SystemClock.uptimeMillis();
+                break;
+            case DELIVERY_DEFERRED:
+                deferredCount++;
+                break;
             case DELIVERY_DELIVERED:
             case DELIVERY_SKIPPED:
             case DELIVERY_TIMEOUT:
             case DELIVERY_FAILURE:
                 terminalTime[index] = SystemClock.uptimeMillis();
-                break;
-            case DELIVERY_SCHEDULED:
-                scheduledTime[index] = SystemClock.uptimeMillis();
-                break;
-            case DELIVERY_DEFERRED:
-                if (deferUntilActive) deferredUntilActive[index] = true;
+                terminalCount++;
                 break;
         }
+
+        delivery[index] = newDeliveryState;
+        deliveryReasons[index] = reason;
+
+        // If this state change might bring us to a new high-water mark, bring
+        // ourselves as high as we possibly can
+        final int oldBeyondCount = beyondCount;
+        if (index >= beyondCount) {
+            for (int i = beyondCount; i < delivery.length; i++) {
+                if (isDeliveryStateBeyond(getDeliveryState(i))) {
+                    beyondCount = i + 1;
+                } else {
+                    break;
+                }
+            }
+        }
+        return (beyondCount != oldBeyondCount);
     }
 
     @DeliveryState int getDeliveryState(int index) {
         return delivery[index];
     }
 
+    /**
+     * @return if the given {@link #receivers} index should be considered
+     *         blocked based on the current status of the overall broadcast.
+     */
+    boolean isBlocked(int index) {
+        return (beyondCount < blockedUntilBeyondCount[index]);
+    }
+
     boolean wasDeliveryAttempted(int index) {
         final int deliveryState = getDeliveryState(index);
         switch (deliveryState) {
@@ -757,36 +813,36 @@
      * has prioritized tranches of receivers.
      */
     @VisibleForTesting
-    static boolean isPrioritized(@NonNull int[] blockedUntilTerminalCount,
+    static boolean isPrioritized(@NonNull int[] blockedUntilBeyondCount,
             boolean ordered) {
-        return !ordered && (blockedUntilTerminalCount.length > 0)
-                && (blockedUntilTerminalCount[0] != -1);
+        return !ordered && (blockedUntilBeyondCount.length > 0)
+                && (blockedUntilBeyondCount[0] != -1);
     }
 
     /**
-     * Calculate the {@link #terminalCount} that each receiver should be
+     * Calculate the {@link #beyondCount} that each receiver should be
      * considered blocked until.
      * <p>
      * For example, in an ordered broadcast, receiver {@code N} is blocked until
-     * receiver {@code N-1} reaches a terminal state. Similarly, in a
-     * prioritized broadcast, receiver {@code N} is blocked until all receivers
-     * of a higher priority reach a terminal state.
+     * receiver {@code N-1} reaches a terminal or deferred state. Similarly, in
+     * a prioritized broadcast, receiver {@code N} is blocked until all
+     * receivers of a higher priority reach a terminal or deferred state.
      * <p>
-     * When there are no terminal count constraints, the blocked value for each
+     * When there are no beyond count constraints, the blocked value for each
      * receiver is {@code -1}.
      */
     @VisibleForTesting
-    static @NonNull int[] calculateBlockedUntilTerminalCount(
+    static @NonNull int[] calculateBlockedUntilBeyondCount(
             @NonNull List<Object> receivers, boolean ordered) {
         final int N = receivers.size();
-        final int[] blockedUntilTerminalCount = new int[N];
+        final int[] blockedUntilBeyondCount = new int[N];
         int lastPriority = 0;
         int lastPriorityIndex = 0;
         for (int i = 0; i < N; i++) {
             if (ordered) {
                 // When sending an ordered broadcast, we need to block this
                 // receiver until all previous receivers have terminated
-                blockedUntilTerminalCount[i] = i;
+                blockedUntilBeyondCount[i] = i;
             } else {
                 // When sending a prioritized broadcast, we only need to wait
                 // for the previous tranche of receivers to be terminated
@@ -794,18 +850,18 @@
                 if ((i == 0) || (thisPriority != lastPriority)) {
                     lastPriority = thisPriority;
                     lastPriorityIndex = i;
-                    blockedUntilTerminalCount[i] = i;
+                    blockedUntilBeyondCount[i] = i;
                 } else {
-                    blockedUntilTerminalCount[i] = lastPriorityIndex;
+                    blockedUntilBeyondCount[i] = lastPriorityIndex;
                 }
             }
         }
         // If the entire list is in the same priority tranche, mark as -1 to
         // indicate that none of them need to wait
-        if (N > 0 && blockedUntilTerminalCount[N - 1] == 0) {
-            Arrays.fill(blockedUntilTerminalCount, -1);
+        if (N > 0 && blockedUntilBeyondCount[N - 1] == 0) {
+            Arrays.fill(blockedUntilBeyondCount, -1);
         }
-        return blockedUntilTerminalCount;
+        return blockedUntilBeyondCount;
     }
 
     static int getReceiverUid(@NonNull Object receiver) {
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 4328efe..1426cfd 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -18,6 +18,28 @@
 
 import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN;
 import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ALLOWLIST;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BIND_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_STOP_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SYSTEM_INIT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UID_IDLE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UNBIND_SERVICE;
 import static android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
 
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_COMPACTION;
@@ -26,6 +48,7 @@
 
 import android.annotation.IntDef;
 import android.app.ActivityManager;
+import android.app.ActivityManagerInternal.OomAdjReason;
 import android.app.ActivityThread;
 import android.app.ApplicationExitInfo;
 import android.app.IApplicationThread;
@@ -139,6 +162,26 @@
             FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_BINDER_TXNS;
     static final int UNFREEZE_REASON_FEATURE_FLAGS =
             FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_FEATURE_FLAGS;
+    static final int UNFREEZE_REASON_SHORT_FGS_TIMEOUT =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_SHORT_FGS_TIMEOUT;
+    static final int UNFREEZE_REASON_SYSTEM_INIT =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_SYSTEM_INIT;
+    static final int UNFREEZE_REASON_BACKUP =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_BACKUP;
+    static final int UNFREEZE_REASON_SHELL =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_SHELL;
+    static final int UNFREEZE_REASON_REMOVE_TASK =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_REMOVE_TASK;
+    static final int UNFREEZE_REASON_UID_IDLE =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_UID_IDLE;
+    static final int UNFREEZE_REASON_STOP_SERVICE =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_STOP_SERVICE;
+    static final int UNFREEZE_REASON_EXECUTING_SERVICE =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_EXECUTING_SERVICE;
+    static final int UNFREEZE_REASON_RESTRICTION_CHANGE =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_RESTRICTION_CHANGE;
+    static final int UNFREEZE_REASON_COMPONENT_DISABLED =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_COMPONENT_DISABLED;
 
     @IntDef(prefix = {"UNFREEZE_REASON_"}, value = {
         UNFREEZE_REASON_NONE,
@@ -160,6 +203,16 @@
         UNFREEZE_REASON_FILE_LOCK_CHECK_FAILURE,
         UNFREEZE_REASON_BINDER_TXNS,
         UNFREEZE_REASON_FEATURE_FLAGS,
+        UNFREEZE_REASON_SHORT_FGS_TIMEOUT,
+        UNFREEZE_REASON_SYSTEM_INIT,
+        UNFREEZE_REASON_BACKUP,
+        UNFREEZE_REASON_SHELL,
+        UNFREEZE_REASON_REMOVE_TASK,
+        UNFREEZE_REASON_UID_IDLE,
+        UNFREEZE_REASON_STOP_SERVICE,
+        UNFREEZE_REASON_EXECUTING_SERVICE,
+        UNFREEZE_REASON_RESTRICTION_CHANGE,
+        UNFREEZE_REASON_COMPONENT_DISABLED,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface UnfreezeReason {}
@@ -289,7 +342,7 @@
 
     private final ActivityManagerGlobalLock mProcLock;
 
-    private final Object mFreezerLock = new Object();
+    public final Object mFreezerLock = new Object();
 
     private final OnPropertiesChangedListener mOnFlagsChangedListener =
             new OnPropertiesChangedListener() {
@@ -710,8 +763,9 @@
                 pw.println("  Apps frozen: " + size);
                 for (int i = 0; i < size; i++) {
                     ProcessRecord app = mFrozenProcesses.valueAt(i);
-                    pw.println("    " + app.mOptRecord.getFreezeUnfreezeTime()
-                            + ": " + app.getPid() + " " + app.processName);
+                    pw.println("    " + app.mOptRecord.getFreezeUnfreezeTime() + ": " + app.getPid()
+                            + " " + app.processName
+                            + (app.mOptRecord.isFreezeSticky() ? " (sticky)" : ""));
                 }
 
                 if (!mPendingCompactionProcesses.isEmpty()) {
@@ -1230,12 +1284,26 @@
 
     @GuardedBy({"mAm", "mProcLock"})
     void freezeAppAsyncLSP(ProcessRecord app) {
+        freezeAppAsyncInternalLSP(app, mFreezerDebounceTimeout, false);
+    }
+
+    @GuardedBy({"mAm", "mProcLock"})
+    void freezeAppAsyncInternalLSP(ProcessRecord app, long delayMillis, boolean force) {
         final ProcessCachedOptimizerRecord opt = app.mOptRecord;
         if (opt.isPendingFreeze()) {
             // Skip redundant DO_FREEZE message
             return;
         }
 
+        if (opt.isFreezeSticky() && !force) {
+            if (DEBUG_FREEZER) {
+                Slog.d(TAG_AM,
+                        "Skip freezing because unfrozen state is sticky pid=" + app.getPid() + " "
+                                + app.processName);
+            }
+            return;
+        }
+
         if (mAm.mConstants.USE_MODERN_TRIM
                 && app.mState.getSetAdj() >= ProcessList.CACHED_APP_MIN_ADJ) {
             final IApplicationThread thread = app.getThread();
@@ -1248,9 +1316,8 @@
             }
         }
         mFreezeHandler.sendMessageDelayed(
-                mFreezeHandler.obtainMessage(
-                    SET_FROZEN_PROCESS_MSG, DO_FREEZE, 0, app),
-                mFreezerDebounceTimeout);
+                mFreezeHandler.obtainMessage(SET_FROZEN_PROCESS_MSG, DO_FREEZE, 0, app),
+                delayMillis);
         opt.setPendingFreeze(true);
         if (DEBUG_FREEZER) {
             Slog.d(TAG_AM, "Async freezing " + app.getPid() + " " + app.processName);
@@ -1258,9 +1325,19 @@
     }
 
     @GuardedBy({"mAm", "mProcLock", "mFreezerLock"})
-    void unfreezeAppInternalLSP(ProcessRecord app, @UnfreezeReason int reason) {
+    void unfreezeAppInternalLSP(ProcessRecord app, @UnfreezeReason int reason, boolean force) {
         final int pid = app.getPid();
         final ProcessCachedOptimizerRecord opt = app.mOptRecord;
+        boolean sticky = opt.isFreezeSticky();
+        if (sticky && !force) {
+            // Sticky freezes will not change their state unless forced out of it.
+            if (DEBUG_FREEZER) {
+                Slog.d(TAG_AM,
+                        "Skip unfreezing because frozen state is sticky pid=" + pid + " "
+                                + app.processName);
+            }
+            return;
+        }
         if (opt.isPendingFreeze()) {
             // Remove pending DO_FREEZE message
             mFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app);
@@ -1273,8 +1350,7 @@
         UidRecord uidRec = app.getUidRecord();
         if (uidRec != null && uidRec.isFrozen()) {
             uidRec.setFrozen(false);
-            mFreezeHandler.removeMessages(UID_FROZEN_STATE_CHANGED_MSG, app);
-            reportOneUidFrozenStateChanged(app.uid, false);
+            postUidFrozenMessage(uidRec.getUid(), false);
         }
 
         opt.setFreezerOverride(false);
@@ -1354,7 +1430,7 @@
     @GuardedBy({"mAm", "mProcLock"})
     void unfreezeAppLSP(ProcessRecord app, @UnfreezeReason int reason) {
         synchronized (mFreezerLock) {
-            unfreezeAppInternalLSP(app, reason);
+            unfreezeAppInternalLSP(app, reason, false);
         }
     }
 
@@ -1365,7 +1441,7 @@
      * The caller of this function should still trigger updateOomAdj for AMS to unfreeze the app.
      * @param pid pid of the process to be unfrozen
      */
-    void unfreezeProcess(int pid, @OomAdjuster.OomAdjReason int reason) {
+    void unfreezeProcess(int pid, @OomAdjReason int reason) {
         synchronized (mFreezerLock) {
             ProcessRecord app = mFrozenProcesses.get(pid);
             if (app == null) {
@@ -1415,8 +1491,7 @@
             UidRecord uidRec = app.getUidRecord();
             if (uidRec != null && uidRec.isFrozen()) {
                 uidRec.setFrozen(false);
-                mFreezeHandler.removeMessages(UID_FROZEN_STATE_CHANGED_MSG, app);
-                reportOneUidFrozenStateChanged(app.uid, false);
+                postUidFrozenMessage(uidRec.getUid(), false);
             }
 
             mFrozenProcesses.delete(app.getPid());
@@ -1546,12 +1621,12 @@
         public long mOrigAnonRss;
         public int mProcState;
         public int mOomAdj;
-        public @OomAdjuster.OomAdjReason int mOomAdjReason;
+        public @OomAdjReason int mOomAdjReason;
 
         SingleCompactionStats(long[] rss, CompactSource source, String processName,
                 long deltaAnonRss, long zramConsumed, long anonMemFreed, long origAnonRss,
                 long cpuTimeMillis, int procState, int oomAdj,
-                @OomAdjuster.OomAdjReason int oomAdjReason, int uid) {
+                @OomAdjReason int oomAdjReason, int uid) {
             mRssAfterCompaction = rss;
             mSourceType = source;
             mProcessName = processName;
@@ -1945,6 +2020,15 @@
         mAm.reportUidFrozenStateChanged(uids, frozenStates);
     }
 
+    private void postUidFrozenMessage(int uid, boolean frozen) {
+        final Integer uidObj = Integer.valueOf(uid);
+        mFreezeHandler.removeEqualMessages(UID_FROZEN_STATE_CHANGED_MSG, uidObj);
+
+        final int op = frozen ? 1 : 0;
+        mFreezeHandler.sendMessage(mFreezeHandler.obtainMessage(UID_FROZEN_STATE_CHANGED_MSG, op,
+                0, uidObj));
+    }
+
     private final class FreezeHandler extends Handler implements
             ProcLocksReader.ProcLocksReaderCallback {
         private FreezeHandler() {
@@ -1975,7 +2059,9 @@
                     reportUnfreeze(pid, frozenDuration, processName, reason);
                     break;
                 case UID_FROZEN_STATE_CHANGED_MSG:
-                    reportOneUidFrozenStateChanged(((ProcessRecord) msg.obj).uid, true);
+                    final boolean frozen = (msg.arg1 == 1);
+                    final int uid = (int) msg.obj;
+                    reportOneUidFrozenStateChanged(uid, frozen);
                     break;
                 case DEADLOCK_WATCHDOG_MSG:
                     try {
@@ -2018,15 +2104,6 @@
 
             synchronized (mProcLock) {
                 pid = proc.getPid();
-                if (proc.mState.getCurAdj() < ProcessList.CACHED_APP_MIN_ADJ
-                        || opt.shouldNotFreeze()) {
-                    if (DEBUG_FREEZER) {
-                        Slog.d(TAG_AM, "Skipping freeze for process " + pid
-                                + " " + name + " curAdj = " + proc.mState.getCurAdj()
-                                + ", shouldNotFreeze = " + opt.shouldNotFreeze());
-                    }
-                    return;
-                }
 
                 if (mFreezerOverride) {
                     opt.setFreezerOverride(true);
@@ -2086,8 +2163,8 @@
                 final UidRecord uidRec = proc.getUidRecord();
                 if (frozen && uidRec != null && uidRec.areAllProcessesFrozen()) {
                     uidRec.setFrozen(true);
-                    mFreezeHandler.sendMessage(mFreezeHandler.obtainMessage(
-                            UID_FROZEN_STATE_CHANGED_MSG, proc));
+
+                    postUidFrozenMessage(uidRec.getUid(), true);
                 }
             }
 
@@ -2207,32 +2284,52 @@
         }
     }
 
-    static int getUnfreezeReasonCodeFromOomAdjReason(@OomAdjuster.OomAdjReason int oomAdjReason) {
+    static int getUnfreezeReasonCodeFromOomAdjReason(@OomAdjReason int oomAdjReason) {
         switch (oomAdjReason) {
-            case OomAdjuster.OOM_ADJ_REASON_ACTIVITY:
+            case OOM_ADJ_REASON_ACTIVITY:
                 return UNFREEZE_REASON_ACTIVITY;
-            case OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER:
+            case OOM_ADJ_REASON_FINISH_RECEIVER:
                 return UNFREEZE_REASON_FINISH_RECEIVER;
-            case OomAdjuster.OOM_ADJ_REASON_START_RECEIVER:
+            case OOM_ADJ_REASON_START_RECEIVER:
                 return UNFREEZE_REASON_START_RECEIVER;
-            case OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE:
+            case OOM_ADJ_REASON_BIND_SERVICE:
                 return UNFREEZE_REASON_BIND_SERVICE;
-            case OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE:
+            case OOM_ADJ_REASON_UNBIND_SERVICE:
                 return UNFREEZE_REASON_UNBIND_SERVICE;
-            case OomAdjuster.OOM_ADJ_REASON_START_SERVICE:
+            case OOM_ADJ_REASON_START_SERVICE:
                 return UNFREEZE_REASON_START_SERVICE;
-            case OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER:
+            case OOM_ADJ_REASON_GET_PROVIDER:
                 return UNFREEZE_REASON_GET_PROVIDER;
-            case OomAdjuster.OOM_ADJ_REASON_REMOVE_PROVIDER:
+            case OOM_ADJ_REASON_REMOVE_PROVIDER:
                 return UNFREEZE_REASON_REMOVE_PROVIDER;
-            case OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY:
+            case OOM_ADJ_REASON_UI_VISIBILITY:
                 return UNFREEZE_REASON_UI_VISIBILITY;
-            case OomAdjuster.OOM_ADJ_REASON_ALLOWLIST:
+            case OOM_ADJ_REASON_ALLOWLIST:
                 return UNFREEZE_REASON_ALLOWLIST;
-            case OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN:
+            case OOM_ADJ_REASON_PROCESS_BEGIN:
                 return UNFREEZE_REASON_PROCESS_BEGIN;
-            case OomAdjuster.OOM_ADJ_REASON_PROCESS_END:
+            case OOM_ADJ_REASON_PROCESS_END:
                 return UNFREEZE_REASON_PROCESS_END;
+            case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT:
+                return UNFREEZE_REASON_SHORT_FGS_TIMEOUT;
+            case OOM_ADJ_REASON_SYSTEM_INIT:
+                return UNFREEZE_REASON_SYSTEM_INIT;
+            case OOM_ADJ_REASON_BACKUP:
+                return UNFREEZE_REASON_BACKUP;
+            case OOM_ADJ_REASON_SHELL:
+                return UNFREEZE_REASON_SHELL;
+            case OOM_ADJ_REASON_REMOVE_TASK:
+                return UNFREEZE_REASON_REMOVE_TASK;
+            case OOM_ADJ_REASON_UID_IDLE:
+                return UNFREEZE_REASON_UID_IDLE;
+            case OOM_ADJ_REASON_STOP_SERVICE:
+                return UNFREEZE_REASON_STOP_SERVICE;
+            case OOM_ADJ_REASON_EXECUTING_SERVICE:
+                return UNFREEZE_REASON_EXECUTING_SERVICE;
+            case OOM_ADJ_REASON_RESTRICTION_CHANGE:
+                return UNFREEZE_REASON_RESTRICTION_CHANGE;
+            case OOM_ADJ_REASON_COMPONENT_DISABLED:
+                return UNFREEZE_REASON_COMPONENT_DISABLED;
             default:
                 return UNFREEZE_REASON_NONE;
         }
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index a1fcd42..d8cb094 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -16,6 +16,8 @@
 package com.android.server.am;
 
 import static android.Manifest.permission.GET_ANY_PROVIDER_TYPE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER;
 import static android.content.ContentProvider.isAuthorityRedirectedForCloneProfile;
 import static android.os.Process.PROC_CHAR;
 import static android.os.Process.PROC_OUT_LONG;
@@ -292,7 +294,7 @@
                     checkTime(startTime, "getContentProviderImpl: before updateOomAdj");
                     final int verifiedAdj = cpr.proc.mState.getVerifiedAdj();
                     boolean success = mService.updateOomAdjLocked(cpr.proc,
-                            OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER);
+                            OOM_ADJ_REASON_GET_PROVIDER);
                     // XXX things have changed so updateOomAdjLocked doesn't actually tell us
                     // if the process has been successfully adjusted.  So to reduce races with
                     // it, we will check whether the process still exists.  Note that this doesn't
@@ -757,7 +759,7 @@
 
             // update the app's oom adj value and each provider's usage stats
             if (providersPublished) {
-                mService.updateOomAdjLocked(r, OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER);
+                mService.updateOomAdjLocked(r, OOM_ADJ_REASON_GET_PROVIDER);
                 for (int i = 0, size = providers.size(); i < size; i++) {
                     ContentProviderHolder src = providers.get(i);
                     if (src == null || src.info == null || src.provider == null) {
@@ -835,8 +837,7 @@
             ContentProviderRecord localCpr = mProviderMap.getProviderByClass(comp, userId);
             if (localCpr.hasExternalProcessHandles()) {
                 if (localCpr.removeExternalProcessHandleLocked(token)) {
-                    mService.updateOomAdjLocked(localCpr.proc,
-                            OomAdjuster.OOM_ADJ_REASON_REMOVE_PROVIDER);
+                    mService.updateOomAdjLocked(localCpr.proc, OOM_ADJ_REASON_REMOVE_PROVIDER);
                 } else {
                     Slog.e(TAG, "Attempt to remove content provider " + localCpr
                             + " with no external reference for token: " + token + ".");
@@ -1506,8 +1507,7 @@
             mService.stopAssociationLocked(conn.client.uid, conn.client.processName, cpr.uid,
                     cpr.appInfo.longVersionCode, cpr.name, cpr.info.processName);
             if (updateOomAdj) {
-                mService.updateOomAdjLocked(conn.provider.proc,
-                        OomAdjuster.OOM_ADJ_REASON_REMOVE_PROVIDER);
+                mService.updateOomAdjLocked(conn.provider.proc, OOM_ADJ_REASON_REMOVE_PROVIDER);
             }
         }
     }
diff --git a/services/core/java/com/android/server/am/DropboxRateLimiter.java b/services/core/java/com/android/server/am/DropboxRateLimiter.java
index 9ff2cd0..727d4df9 100644
--- a/services/core/java/com/android/server/am/DropboxRateLimiter.java
+++ b/services/core/java/com/android/server/am/DropboxRateLimiter.java
@@ -22,7 +22,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.expresslog.Counter;
+import com.android.modules.expresslog.Counter;
 
 /** Rate limiter for adding errors into dropbox. */
 public class DropboxRateLimiter {
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index a98571b..365dcd9 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -41,6 +41,29 @@
 import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
 import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ALLOWLIST;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BIND_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_STOP_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SYSTEM_INIT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UID_IDLE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UNBIND_SERVICE;
 import static android.content.Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
@@ -101,9 +124,9 @@
 import static com.android.server.am.ProcessList.VISIBLE_APP_ADJ;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
 
-import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.ActivityManagerInternal.OomAdjReason;
 import android.app.ActivityThread;
 import android.app.AppProtoEnums;
 import android.app.ApplicationExitInfo;
@@ -141,8 +164,6 @@
 import com.android.server.wm.WindowProcessController;
 
 import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -154,32 +175,6 @@
 public class OomAdjuster {
     static final String TAG = "OomAdjuster";
 
-    static final int OOM_ADJ_REASON_NONE = 0;
-    static final int OOM_ADJ_REASON_ACTIVITY = 1;
-    static final int OOM_ADJ_REASON_FINISH_RECEIVER = 2;
-    static final int OOM_ADJ_REASON_START_RECEIVER = 3;
-    static final int OOM_ADJ_REASON_BIND_SERVICE = 4;
-    static final int OOM_ADJ_REASON_UNBIND_SERVICE = 5;
-    static final int OOM_ADJ_REASON_START_SERVICE = 6;
-    static final int OOM_ADJ_REASON_GET_PROVIDER = 7;
-    static final int OOM_ADJ_REASON_REMOVE_PROVIDER = 8;
-    static final int OOM_ADJ_REASON_UI_VISIBILITY = 9;
-    static final int OOM_ADJ_REASON_ALLOWLIST = 10;
-    static final int OOM_ADJ_REASON_PROCESS_BEGIN = 11;
-    static final int OOM_ADJ_REASON_PROCESS_END = 12;
-    static final int OOM_ADJ_REASON_SHORT_FGS_TIMEOUT = 13;
-
-    @IntDef(prefix = {"OOM_ADJ_REASON_"},
-            value = {OOM_ADJ_REASON_NONE, OOM_ADJ_REASON_ACTIVITY, OOM_ADJ_REASON_FINISH_RECEIVER,
-                    OOM_ADJ_REASON_START_RECEIVER, OOM_ADJ_REASON_BIND_SERVICE,
-                    OOM_ADJ_REASON_UNBIND_SERVICE, OOM_ADJ_REASON_START_SERVICE,
-                    OOM_ADJ_REASON_GET_PROVIDER, OOM_ADJ_REASON_REMOVE_PROVIDER,
-                    OOM_ADJ_REASON_UI_VISIBILITY, OOM_ADJ_REASON_ALLOWLIST,
-                    OOM_ADJ_REASON_PROCESS_BEGIN, OOM_ADJ_REASON_PROCESS_END,
-                    OOM_ADJ_REASON_SHORT_FGS_TIMEOUT})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface OomAdjReason {}
-
     public static final int oomAdjReasonToProto(@OomAdjReason int oomReason) {
         switch (oomReason) {
             case OOM_ADJ_REASON_NONE:
@@ -210,6 +205,24 @@
                 return AppProtoEnums.OOM_ADJ_REASON_PROCESS_END;
             case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT:
                 return AppProtoEnums.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
+            case OOM_ADJ_REASON_SYSTEM_INIT:
+                return AppProtoEnums.OOM_ADJ_REASON_SYSTEM_INIT;
+            case OOM_ADJ_REASON_BACKUP:
+                return AppProtoEnums.OOM_ADJ_REASON_BACKUP;
+            case OOM_ADJ_REASON_SHELL:
+                return AppProtoEnums.OOM_ADJ_REASON_SHELL;
+            case OOM_ADJ_REASON_REMOVE_TASK:
+                return AppProtoEnums.OOM_ADJ_REASON_REMOVE_TASK;
+            case OOM_ADJ_REASON_UID_IDLE:
+                return AppProtoEnums.OOM_ADJ_REASON_UID_IDLE;
+            case OOM_ADJ_REASON_STOP_SERVICE:
+                return AppProtoEnums.OOM_ADJ_REASON_STOP_SERVICE;
+            case OOM_ADJ_REASON_EXECUTING_SERVICE:
+                return AppProtoEnums.OOM_ADJ_REASON_EXECUTING_SERVICE;
+            case OOM_ADJ_REASON_RESTRICTION_CHANGE:
+                return AppProtoEnums.OOM_ADJ_REASON_RESTRICTION_CHANGE;
+            case OOM_ADJ_REASON_COMPONENT_DISABLED:
+                return AppProtoEnums.OOM_ADJ_REASON_COMPONENT_DISABLED;
             default:
                 return AppProtoEnums.OOM_ADJ_REASON_UNKNOWN_TO_PROTO;
         }
@@ -246,6 +259,24 @@
                 return OOM_ADJ_REASON_METHOD + "_processEnd";
             case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT:
                 return OOM_ADJ_REASON_METHOD + "_shortFgs";
+            case OOM_ADJ_REASON_SYSTEM_INIT:
+                return OOM_ADJ_REASON_METHOD + "_systemInit";
+            case OOM_ADJ_REASON_BACKUP:
+                return OOM_ADJ_REASON_METHOD + "_backup";
+            case OOM_ADJ_REASON_SHELL:
+                return OOM_ADJ_REASON_METHOD + "_shell";
+            case OOM_ADJ_REASON_REMOVE_TASK:
+                return OOM_ADJ_REASON_METHOD + "_removeTask";
+            case OOM_ADJ_REASON_UID_IDLE:
+                return OOM_ADJ_REASON_METHOD + "_uidIdle";
+            case OOM_ADJ_REASON_STOP_SERVICE:
+                return OOM_ADJ_REASON_METHOD + "_stopService";
+            case OOM_ADJ_REASON_EXECUTING_SERVICE:
+                return OOM_ADJ_REASON_METHOD + "_executingService";
+            case OOM_ADJ_REASON_RESTRICTION_CHANGE:
+                return OOM_ADJ_REASON_METHOD + "_restrictionChange";
+            case OOM_ADJ_REASON_COMPONENT_DISABLED:
+                return OOM_ADJ_REASON_METHOD + "_componentDisabled";
             default:
                 return "_unknown";
         }
@@ -874,8 +905,7 @@
     }
 
     @GuardedBy("mService")
-    private void performUpdateOomAdjPendingTargetsLocked(
-            @OomAdjuster.OomAdjReason int oomAdjReason) {
+    private void performUpdateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
         final ProcessRecord topApp = mService.getTopApp();
 
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
@@ -3453,7 +3483,7 @@
     }
 
     @GuardedBy("mService")
-    void unfreezeTemporarily(ProcessRecord app, @OomAdjuster.OomAdjReason int reason) {
+    void unfreezeTemporarily(ProcessRecord app, @OomAdjReason int reason) {
         if (!mCachedAppOptimizer.useFreezer()) {
             return;
         }
diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
index 24cc533..e8c8f6d 100644
--- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
+++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
@@ -16,6 +16,8 @@
 
 package com.android.server.am;
 
+import android.app.ActivityManagerInternal.OomAdjReason;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -51,7 +53,7 @@
     /**
      * Last oom adjust change reason for this app.
      */
-    @GuardedBy("mProcLock") private @OomAdjuster.OomAdjReason int mLastOomAdjChangeReason;
+    @GuardedBy("mProcLock") private @OomAdjReason int mLastOomAdjChangeReason;
 
     /**
      * The most recent compaction action performed for this app.
@@ -73,6 +75,15 @@
     private boolean mFrozen;
 
     /**
+     * If set to true it will make the (un)freeze decision sticky which means that the freezer
+     * decision will remain the same unless a freeze is forced via {@link #mForceFreezeOps}.
+     * This property is usually set to true when external user wants to maintain a (un)frozen state
+     * after being applied.
+     */
+    @GuardedBy("mProcLock")
+    private boolean mFreezeSticky;
+
+    /**
      * Set to false after the process has been frozen.
      * Set to true after we have collected PSS for the frozen process.
      */
@@ -139,12 +150,12 @@
     }
 
     @GuardedBy("mProcLock")
-    void setLastOomAdjChangeReason(@OomAdjuster.OomAdjReason int reason) {
+    void setLastOomAdjChangeReason(@OomAdjReason int reason) {
         mLastOomAdjChangeReason = reason;
     }
 
     @GuardedBy("mProcLock")
-    @OomAdjuster.OomAdjReason
+    @OomAdjReason
     int getLastOomAdjChangeReason() {
         return mLastOomAdjChangeReason;
     }
@@ -193,6 +204,15 @@
     void setFrozen(boolean frozen) {
         mFrozen = frozen;
     }
+    @GuardedBy("mProcLock")
+    void setFreezeSticky(boolean sticky) {
+        mFreezeSticky = sticky;
+    }
+
+    @GuardedBy("mProcLock")
+    boolean isFreezeSticky() {
+        return mFreezeSticky;
+    }
 
     boolean skipPSSCollectionBecauseFrozen() {
         boolean collected = mHasCollectedFrozenPSS;
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 70a696c..ca41f42 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -51,7 +51,7 @@
 import com.android.internal.annotations.CompositeRWLock;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.expresslog.Counter;
+import com.android.modules.expresslog.Counter;
 import com.android.internal.os.ProcessCpuTracker;
 import com.android.internal.os.TimeoutRecord;
 import com.android.internal.os.anr.AnrLatencyTracker;
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index b1322ef..312f98a 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -19,6 +19,8 @@
 import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
 import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE;
 import static android.app.ActivityThread.PROC_START_SEQ_IDENT;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
 import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode;
@@ -2875,7 +2877,7 @@
                     reasonCode, subReason, reason, !doFreeze /* async */);
         }
         killAppZygotesLocked(packageName, appId, userId, false /* force */);
-        mService.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_PROCESS_END);
+        mService.updateOomAdjLocked(OOM_ADJ_REASON_PROCESS_END);
         if (doFreeze) {
             freezePackageCgroup(packageUID, false);
         }
@@ -5140,7 +5142,7 @@
                 }
             });
             /* Will be a no-op if nothing pending */
-            mService.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+            mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_RESTRICTION_CHANGE);
         }
     }
 
@@ -5203,6 +5205,17 @@
     }
 
     /**
+     * Called by ActivityManagerService when a recoverable native crash occurs.
+     */
+    @GuardedBy("mService")
+    void noteAppRecoverableCrash(final ProcessRecord app) {
+        if (DEBUG_PROCESSES) {
+            Slog.i(TAG, "note: " + app + " has a recoverable native crash");
+        }
+        mAppExitInfoTracker.scheduleNoteAppRecoverableCrash(app);
+    }
+
+    /**
      * Called by ActivityManagerService when it decides to kill an application process.
      */
     @GuardedBy("mService")
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index afae623..ffb40ee 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -16,6 +16,8 @@
 
 package com.android.server.am;
 
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+
 import static com.android.internal.util.Preconditions.checkArgument;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -577,7 +579,9 @@
         processName = _processName;
         sdkSandboxClientAppPackage = _sdkSandboxClientAppPackage;
         if (isSdkSandbox) {
-            sdkSandboxClientAppVolumeUuid = getClientInfoForSdkSandbox().volumeUuid;
+            final ApplicationInfo clientInfo = getClientInfoForSdkSandbox();
+            sdkSandboxClientAppVolumeUuid = clientInfo != null
+                    ? clientInfo.volumeUuid : null;
         } else {
             sdkSandboxClientAppVolumeUuid = null;
         }
@@ -1450,7 +1454,7 @@
             }
             mService.updateLruProcessLocked(this, activityChange, null /* client */);
             if (updateOomAdj) {
-                mService.updateOomAdjLocked(this, OomAdjuster.OOM_ADJ_REASON_ACTIVITY);
+                mService.updateOomAdjLocked(this, OOM_ADJ_REASON_ACTIVITY);
             }
         }
     }
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index 71d5d39..ab71acd 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -18,6 +18,7 @@
 
 import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
 
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
 import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_ACTIVITY;
@@ -613,7 +614,7 @@
     void forceProcessStateUpTo(int newState) {
         if (mRepProcState > newState) {
             synchronized (mProcLock) {
-                mRepProcState = newState;
+                setReportedProcState(newState);
                 setCurProcState(newState);
                 setCurRawProcState(newState);
             }
@@ -766,7 +767,7 @@
             Slog.i(TAG, "Setting runningRemoteAnimation=" + runningRemoteAnimation
                     + " for pid=" + mApp.getPid());
         }
-        mService.updateOomAdjLocked(mApp, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+        mService.updateOomAdjLocked(mApp, OOM_ADJ_REASON_UI_VISIBILITY);
     }
 
     @GuardedBy({"mService", "mProcLock"})
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index edf0dbd..a875860 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -174,6 +174,8 @@
     // allow while-in-use permissions in foreground service or not.
     // while-in-use permissions in FGS started from background might be restricted.
     boolean mAllowWhileInUsePermissionInFgs;
+    @PowerExemptionManager.ReasonCode
+    int mAllowWhileInUsePermissionInFgsReason;
     // A copy of mAllowWhileInUsePermissionInFgs's value when the service is entering FGS state.
     boolean mAllowWhileInUsePermissionInFgsAtEntering;
     /** Allow scheduling user-initiated jobs from the background. */
@@ -609,6 +611,8 @@
         }
         pw.print(prefix); pw.print("allowWhileInUsePermissionInFgs=");
                 pw.println(mAllowWhileInUsePermissionInFgs);
+        pw.print(prefix); pw.print("mAllowWhileInUsePermissionInFgsReason=");
+        pw.println(mAllowWhileInUsePermissionInFgsReason);
         pw.print(prefix); pw.print("allowUiJobScheduling="); pw.println(mAllowUiJobScheduling);
         pw.print(prefix); pw.print("recentCallingPackage=");
                 pw.println(mRecentCallingPackage);
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 22e2c9f..8c227f5 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -100,7 +100,6 @@
         DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
         DeviceConfig.NAMESPACE_SURFACE_FLINGER_NATIVE_BOOT,
         DeviceConfig.NAMESPACE_SWCODEC_NATIVE,
-        DeviceConfig.NAMESPACE_TETHERING,
         DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE,
         DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT,
         DeviceConfig.NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE,
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 81ba4b8..a110169 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -34,6 +34,7 @@
 import static android.app.AppOpsManager.MODE_FOREGROUND;
 import static android.app.AppOpsManager.MODE_IGNORED;
 import static android.app.AppOpsManager.OP_CAMERA;
+import static android.app.AppOpsManager.OP_CAMERA_SANDBOXED;
 import static android.app.AppOpsManager.OP_FLAGS_ALL;
 import static android.app.AppOpsManager.OP_FLAG_SELF;
 import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
@@ -42,6 +43,7 @@
 import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
 import static android.app.AppOpsManager.OP_RECORD_AUDIO;
 import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO_SANDBOXED;
 import static android.app.AppOpsManager.OP_VIBRATE;
 import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED;
 import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED;
@@ -3027,17 +3029,29 @@
                     packageName);
         }
 
-        // As a special case for OP_RECORD_AUDIO_HOTWORD, which we use only for attribution
-        // purposes and not as a check, also make sure that the caller is allowed to access
-        // the data gated by OP_RECORD_AUDIO.
+        // As a special case for OP_RECORD_AUDIO_HOTWORD, OP_RECEIVE_AMBIENT_TRIGGER_AUDIO and
+        // OP_RECORD_AUDIO_SANDBOXED which we use only for attribution purposes and not as a check,
+        // also make sure that the caller is allowed to access the data gated by OP_RECORD_AUDIO.
         //
         // TODO: Revert this change before Android 12.
-        if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO) {
-            int result = checkOperation(OP_RECORD_AUDIO, uid, packageName);
+        int result = MODE_DEFAULT;
+        if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO
+                || code == OP_RECORD_AUDIO_SANDBOXED) {
+            result = checkOperation(OP_RECORD_AUDIO, uid, packageName);
+            // Check result
             if (result != AppOpsManager.MODE_ALLOWED) {
                 return new SyncNotedAppOp(result, code, attributionTag, packageName);
             }
         }
+        // As a special case for OP_CAMERA_SANDBOXED.
+        if (code == OP_CAMERA_SANDBOXED) {
+            result = checkOperation(OP_CAMERA, uid, packageName);
+            // Check result
+            if (result != AppOpsManager.MODE_ALLOWED) {
+                return new SyncNotedAppOp(result, code, attributionTag, packageName);
+            }
+        }
+
         return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
                 Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault,
                 shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags,
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 1268156..462942e 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -87,14 +87,14 @@
     private final @NonNull Context mContext;
 
     /** ID for Communication strategy retrieved form audio policy manager */
-    private int mCommunicationStrategyId = -1;
+    /*package*/  int mCommunicationStrategyId = -1;
 
     /** ID for Accessibility strategy retrieved form audio policy manager */
     private int mAccessibilityStrategyId = -1;
 
 
     /** Active communication device reported by audio policy manager */
-    private AudioDeviceInfo mActiveCommunicationDevice;
+    /*package*/ AudioDeviceInfo mActiveCommunicationDevice;
     /** Last preferred device set for communication strategy */
     private AudioDeviceAttributes mPreferredCommunicationDevice;
 
@@ -204,6 +204,7 @@
     private void init() {
         setupMessaging(mContext);
 
+        initAudioHalBluetoothState();
         initRoutingStrategyIds();
         mPreferredCommunicationDevice = null;
         updateActiveCommunicationDevice();
@@ -749,6 +750,19 @@
             mIsLeOutput = false;
         }
 
+        BtDeviceInfo(@NonNull BtDeviceInfo src, int state) {
+            mDevice = src.mDevice;
+            mState = state;
+            mProfile = src.mProfile;
+            mSupprNoisy = src.mSupprNoisy;
+            mVolume = src.mVolume;
+            mIsLeOutput = src.mIsLeOutput;
+            mEventSource = src.mEventSource;
+            mAudioSystemDevice = src.mAudioSystemDevice;
+            mMusicDevice = src.mMusicDevice;
+            mCodec = src.mCodec;
+        }
+
         // redefine equality op so we can match messages intended for this device
         @Override
         public boolean equals(Object o) {
@@ -815,7 +829,7 @@
      * @param info struct with the (dis)connection information
      */
     /*package*/ void queueOnBluetoothActiveDeviceChanged(@NonNull BtDeviceChangedData data) {
-        if (data.mInfo.getProfile() == BluetoothProfile.A2DP && data.mPreviousDevice != null
+        if (data.mPreviousDevice != null
                 && data.mPreviousDevice.equals(data.mNewDevice)) {
             final String name = TextUtils.emptyIfNull(data.mNewDevice.getName());
             new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE + MediaMetrics.SEPARATOR
@@ -824,7 +838,8 @@
                     .set(MediaMetrics.Property.STATUS, data.mInfo.getProfile())
                     .record();
             synchronized (mDeviceStateLock) {
-                postBluetoothA2dpDeviceConfigChange(data.mNewDevice);
+                postBluetoothDeviceConfigChange(createBtDeviceInfo(data, data.mNewDevice,
+                        BluetoothProfile.STATE_CONNECTED));
             }
         } else {
             synchronized (mDeviceStateLock) {
@@ -845,10 +860,100 @@
         }
     }
 
-    /**
-     * Current Bluetooth SCO audio active state indicated by BtHelper via setBluetoothScoOn().
-     */
+    // Current Bluetooth SCO audio active state indicated by BtHelper via setBluetoothScoOn().
+    @GuardedBy("mDeviceStateLock")
     private boolean mBluetoothScoOn;
+    // value of BT_SCO parameter currently applied to audio HAL.
+    @GuardedBy("mDeviceStateLock")
+    private boolean mBluetoothScoOnApplied;
+
+    // A2DP suspend state requested by AudioManager.setA2dpSuspended() API.
+    @GuardedBy("mDeviceStateLock")
+    private boolean mBluetoothA2dpSuspendedExt;
+    // A2DP suspend state requested by AudioDeviceInventory.
+    @GuardedBy("mDeviceStateLock")
+    private boolean mBluetoothA2dpSuspendedInt;
+    // value of BT_A2dpSuspendedSCO parameter currently applied to audio HAL.
+    @GuardedBy("mDeviceStateLock")
+    private boolean mBluetoothA2dpSuspendedApplied;
+
+    // LE Audio suspend state requested by AudioManager.setLeAudioSuspended() API.
+    @GuardedBy("mDeviceStateLock")
+    private boolean mBluetoothLeSuspendedExt;
+    // LE Audio suspend state requested by AudioDeviceInventory.
+    @GuardedBy("mDeviceStateLock")
+    private boolean mBluetoothLeSuspendedInt;
+    // value of LeAudioSuspended parameter currently applied to audio HAL.
+    @GuardedBy("mDeviceStateLock")
+    private boolean mBluetoothLeSuspendedApplied;
+
+    private void initAudioHalBluetoothState() {
+        mBluetoothScoOnApplied = false;
+        AudioSystem.setParameters("BT_SCO=off");
+        mBluetoothA2dpSuspendedApplied = false;
+        AudioSystem.setParameters("A2dpSuspended=false");
+        mBluetoothLeSuspendedApplied = false;
+        AudioSystem.setParameters("LeAudioSuspended=false");
+    }
+
+    @GuardedBy("mDeviceStateLock")
+    private void updateAudioHalBluetoothState() {
+        if (mBluetoothScoOn != mBluetoothScoOnApplied) {
+            if (AudioService.DEBUG_COMM_RTE) {
+                Log.v(TAG, "updateAudioHalBluetoothState() mBluetoothScoOn: "
+                        + mBluetoothScoOn + ", mBluetoothScoOnApplied: " + mBluetoothScoOnApplied);
+            }
+            if (mBluetoothScoOn) {
+                if (!mBluetoothA2dpSuspendedApplied) {
+                    AudioSystem.setParameters("A2dpSuspended=true");
+                    mBluetoothA2dpSuspendedApplied = true;
+                }
+                if (!mBluetoothLeSuspendedApplied) {
+                    AudioSystem.setParameters("LeAudioSuspended=true");
+                    mBluetoothLeSuspendedApplied = true;
+                }
+                AudioSystem.setParameters("BT_SCO=on");
+            } else {
+                AudioSystem.setParameters("BT_SCO=off");
+            }
+            mBluetoothScoOnApplied = mBluetoothScoOn;
+        }
+        if (!mBluetoothScoOnApplied) {
+            if ((mBluetoothA2dpSuspendedExt || mBluetoothA2dpSuspendedInt)
+                    != mBluetoothA2dpSuspendedApplied) {
+                if (AudioService.DEBUG_COMM_RTE) {
+                    Log.v(TAG, "updateAudioHalBluetoothState() mBluetoothA2dpSuspendedExt: "
+                            + mBluetoothA2dpSuspendedExt
+                            + ", mBluetoothA2dpSuspendedInt: " + mBluetoothA2dpSuspendedInt
+                            + ", mBluetoothA2dpSuspendedApplied: "
+                            + mBluetoothA2dpSuspendedApplied);
+                }
+                mBluetoothA2dpSuspendedApplied =
+                        mBluetoothA2dpSuspendedExt || mBluetoothA2dpSuspendedInt;
+                if (mBluetoothA2dpSuspendedApplied) {
+                    AudioSystem.setParameters("A2dpSuspended=true");
+                } else {
+                    AudioSystem.setParameters("A2dpSuspended=false");
+                }
+            }
+            if ((mBluetoothLeSuspendedExt || mBluetoothLeSuspendedInt)
+                    != mBluetoothLeSuspendedApplied) {
+                if (AudioService.DEBUG_COMM_RTE) {
+                    Log.v(TAG, "updateAudioHalBluetoothState() mBluetoothLeSuspendedExt: "
+                            + mBluetoothLeSuspendedExt
+                            + ", mBluetoothLeSuspendedInt: " + mBluetoothLeSuspendedInt
+                            + ", mBluetoothLeSuspendedApplied: " + mBluetoothLeSuspendedApplied);
+                }
+                mBluetoothLeSuspendedApplied =
+                        mBluetoothLeSuspendedExt || mBluetoothLeSuspendedInt;
+                if (mBluetoothLeSuspendedApplied) {
+                    AudioSystem.setParameters("LeAudioSuspended=true");
+                } else {
+                    AudioSystem.setParameters("LeAudioSuspended=false");
+                }
+            }
+        }
+    }
 
     /*package*/ void setBluetoothScoOn(boolean on, String eventSource) {
         if (AudioService.DEBUG_COMM_RTE) {
@@ -856,10 +961,67 @@
         }
         synchronized (mDeviceStateLock) {
             mBluetoothScoOn = on;
+            updateAudioHalBluetoothState();
             postUpdateCommunicationRouteClient(eventSource);
         }
     }
 
+    /*package*/ void setA2dpSuspended(boolean enable, boolean internal, String eventSource) {
+        if (AudioService.DEBUG_COMM_RTE) {
+            Log.v(TAG, "setA2dpSuspended source: " + eventSource + ", enable: "
+                    + enable + ", internal: " + internal
+                    + ", mBluetoothA2dpSuspendedInt: " + mBluetoothA2dpSuspendedInt
+                    + ", mBluetoothA2dpSuspendedExt: " + mBluetoothA2dpSuspendedExt);
+        }
+        synchronized (mDeviceStateLock) {
+            if (internal) {
+                mBluetoothA2dpSuspendedInt = enable;
+            } else {
+                mBluetoothA2dpSuspendedExt = enable;
+            }
+            updateAudioHalBluetoothState();
+        }
+    }
+
+    /*package*/ void clearA2dpSuspended() {
+        if (AudioService.DEBUG_COMM_RTE) {
+            Log.v(TAG, "clearA2dpSuspended");
+        }
+        synchronized (mDeviceStateLock) {
+            mBluetoothA2dpSuspendedInt = false;
+            mBluetoothA2dpSuspendedExt = false;
+            updateAudioHalBluetoothState();
+        }
+    }
+
+    /*package*/ void setLeAudioSuspended(boolean enable, boolean internal, String eventSource) {
+        if (AudioService.DEBUG_COMM_RTE) {
+            Log.v(TAG, "setLeAudioSuspended source: " + eventSource + ", enable: "
+                    + enable + ", internal: " + internal
+                    + ", mBluetoothLeSuspendedInt: " + mBluetoothA2dpSuspendedInt
+                    + ", mBluetoothLeSuspendedExt: " + mBluetoothA2dpSuspendedExt);
+        }
+        synchronized (mDeviceStateLock) {
+            if (internal) {
+                mBluetoothLeSuspendedInt = enable;
+            } else {
+                mBluetoothLeSuspendedExt = enable;
+            }
+            updateAudioHalBluetoothState();
+        }
+    }
+
+    /*package*/ void clearLeAudioSuspended() {
+        if (AudioService.DEBUG_COMM_RTE) {
+            Log.v(TAG, "clearLeAudioSuspended");
+        }
+        synchronized (mDeviceStateLock) {
+            mBluetoothLeSuspendedInt = false;
+            mBluetoothLeSuspendedExt = false;
+            updateAudioHalBluetoothState();
+        }
+    }
+
     /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) {
         synchronized (mDeviceStateLock) {
             return mDeviceInventory.startWatchingRoutes(observer);
@@ -902,8 +1064,8 @@
                 new AudioModeInfo(mode, pid, uid));
     }
 
-    /*package*/ void postBluetoothA2dpDeviceConfigChange(@NonNull BluetoothDevice device) {
-        sendLMsgNoDelay(MSG_L_A2DP_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, device);
+    /*package*/ void postBluetoothDeviceConfigChange(@NonNull BtDeviceInfo info) {
+        sendLMsgNoDelay(MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, info);
     }
 
     /*package*/ void startBluetoothScoForClient(IBinder cb, int pid, int scoAudioMode,
@@ -1160,6 +1322,10 @@
         sendIMsgNoDelay(MSG_I_SCO_AUDIO_STATE_CHANGED, SENDMSG_QUEUE, state);
     }
 
+    /*package*/ void postNotifyPreferredAudioProfileApplied(BluetoothDevice btDevice) {
+        sendLMsgNoDelay(MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED, SENDMSG_QUEUE, btDevice);
+    }
+
     /*package*/ static final class CommunicationDeviceInfo {
         final @NonNull IBinder mCb; // Identifies the requesting client for death handler
         final int mPid; // Requester process ID
@@ -1235,9 +1401,11 @@
         }
     }
 
-    /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes, boolean connect) {
+    /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes,
+                                boolean connect, @Nullable BluetoothDevice btDevice) {
         synchronized (mDeviceStateLock) {
-            return mDeviceInventory.handleDeviceConnection(attributes, connect, false /*for test*/);
+            return mDeviceInventory.handleDeviceConnection(
+                    attributes, connect, false /*for test*/, btDevice);
         }
     }
 
@@ -1478,13 +1646,10 @@
                                 (String) msg.obj, msg.arg1);
                     }
                     break;
-                case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
-                    final BluetoothDevice btDevice = (BluetoothDevice) msg.obj;
+                case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE:
                     synchronized (mDeviceStateLock) {
-                        final int a2dpCodec = mBtHelper.getA2dpCodec(btDevice);
-                        mDeviceInventory.onBluetoothA2dpDeviceConfigChange(
-                                new BtHelper.BluetoothA2dpDeviceInfo(btDevice, -1, a2dpCodec),
-                                        BtHelper.EVENT_DEVICE_CONFIG_CHANGE);
+                        mDeviceInventory.onBluetoothDeviceConfigChange(
+                                (BtDeviceInfo) msg.obj, BtHelper.EVENT_DEVICE_CONFIG_CHANGE);
                     }
                     break;
                 case MSG_BROADCAST_AUDIO_BECOMING_NOISY:
@@ -1642,6 +1807,10 @@
                     final int capturePreset = msg.arg1;
                     mDeviceInventory.onSaveClearPreferredDevicesForCapturePreset(capturePreset);
                 } break;
+                case MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED: {
+                    final BluetoothDevice btDevice = (BluetoothDevice) msg.obj;
+                    BtHelper.onNotifyPreferredAudioProfileApplied(btDevice);
+                } break;
                 default:
                     Log.wtf(TAG, "Invalid message " + msg.what);
             }
@@ -1677,7 +1846,7 @@
     private static final int MSG_IL_BTA2DP_TIMEOUT = 10;
 
     // process change of A2DP device configuration, obj is BluetoothDevice
-    private static final int MSG_L_A2DP_DEVICE_CONFIG_CHANGE = 11;
+    private static final int MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE = 11;
 
     private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 12;
     private static final int MSG_REPORT_NEW_ROUTES = 13;
@@ -1717,13 +1886,15 @@
     private static final int MSG_IL_SAVE_REMOVE_NDEF_DEVICE_FOR_STRATEGY = 48;
     private static final int MSG_IL_BTLEAUDIO_TIMEOUT = 49;
 
+    private static final int MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED = 50;
+
     private static boolean isMessageHandledUnderWakelock(int msgId) {
         switch(msgId) {
             case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
             case MSG_L_SET_BT_ACTIVE_DEVICE:
             case MSG_IL_BTA2DP_TIMEOUT:
             case MSG_IL_BTLEAUDIO_TIMEOUT:
-            case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
+            case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE:
             case MSG_TOGGLE_HDMI:
             case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT:
             case MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT:
@@ -1815,7 +1986,7 @@
                 case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
                 case MSG_IL_BTA2DP_TIMEOUT:
                 case MSG_IL_BTLEAUDIO_TIMEOUT:
-                case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
+                case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE:
                     if (sLastDeviceConnectMsgTime >= time) {
                         // add a little delay to make sure messages are ordered as expected
                         time = sLastDeviceConnectMsgTime + 30;
@@ -1835,7 +2006,7 @@
     static {
         MESSAGES_MUTE_MUSIC = new HashSet<>();
         MESSAGES_MUTE_MUSIC.add(MSG_L_SET_BT_ACTIVE_DEVICE);
-        MESSAGES_MUTE_MUSIC.add(MSG_L_A2DP_DEVICE_CONFIG_CHANGE);
+        MESSAGES_MUTE_MUSIC.add(MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE);
         MESSAGES_MUTE_MUSIC.add(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT);
         MESSAGES_MUTE_MUSIC.add(MSG_IIL_SET_FORCE_BT_A2DP_USE);
     }
@@ -1856,7 +2027,7 @@
         // Do not mute on bluetooth event if music is playing on a wired headset.
         if ((message == MSG_L_SET_BT_ACTIVE_DEVICE
                 || message == MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT
-                || message == MSG_L_A2DP_DEVICE_CONFIG_CHANGE)
+                || message == MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE)
                 && AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)
                 && hasIntersection(mDeviceInventory.DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET,
                         mAudioService.getDeviceSetForStream(AudioSystem.STREAM_MUSIC))) {
@@ -1992,27 +2163,22 @@
                 "updateCommunicationRoute, preferredCommunicationDevice: "
                 + preferredCommunicationDevice + " eventSource: " + eventSource)));
 
-        if (preferredCommunicationDevice == null
-                || preferredCommunicationDevice.getType() != AudioDeviceInfo.TYPE_BLUETOOTH_SCO) {
-            AudioSystem.setParameters("BT_SCO=off");
-        } else {
-            AudioSystem.setParameters("BT_SCO=on");
-        }
         if (preferredCommunicationDevice == null) {
             AudioDeviceAttributes defaultDevice = getDefaultCommunicationDevice();
             if (defaultDevice != null) {
-                setPreferredDevicesForStrategySync(
+                mDeviceInventory.setPreferredDevicesForStrategy(
                         mCommunicationStrategyId, Arrays.asList(defaultDevice));
-                setPreferredDevicesForStrategySync(
+                mDeviceInventory.setPreferredDevicesForStrategy(
                         mAccessibilityStrategyId, Arrays.asList(defaultDevice));
             } else {
-                removePreferredDevicesForStrategySync(mCommunicationStrategyId);
-                removePreferredDevicesForStrategySync(mAccessibilityStrategyId);
+                mDeviceInventory.removePreferredDevicesForStrategy(mCommunicationStrategyId);
+                mDeviceInventory.removePreferredDevicesForStrategy(mAccessibilityStrategyId);
             }
+            mDeviceInventory.applyConnectedDevicesRoles();
         } else {
-            setPreferredDevicesForStrategySync(
+            mDeviceInventory.setPreferredDevicesForStrategy(
                     mCommunicationStrategyId, Arrays.asList(preferredCommunicationDevice));
-            setPreferredDevicesForStrategySync(
+            mDeviceInventory.setPreferredDevicesForStrategy(
                     mAccessibilityStrategyId, Arrays.asList(preferredCommunicationDevice));
         }
         onUpdatePhoneStrategyDevice(preferredCommunicationDevice);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 43063af..1eb39f7 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -34,26 +34,36 @@
 import android.media.IStrategyNonDefaultDevicesDispatcher;
 import android.media.IStrategyPreferredDevicesDispatcher;
 import android.media.MediaMetrics;
+import android.media.MediaRecorder.AudioSource;
+import android.media.audiopolicy.AudioProductStrategy;
 import android.media.permission.ClearCallingIdentityContext;
 import android.media.permission.SafeCloseable;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.Pair;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.utils.EventLogger;
 
+import com.google.android.collect.Sets;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
@@ -175,18 +185,26 @@
     final RemoteCallbackList<ICapturePresetDevicesRoleDispatcher> mDevRoleCapturePresetDispatchers =
             new RemoteCallbackList<ICapturePresetDevicesRoleDispatcher>();
 
+    final List<AudioProductStrategy> mStrategies;
+
     /*package*/ AudioDeviceInventory(@NonNull AudioDeviceBroker broker) {
-        mDeviceBroker = broker;
-        mAudioSystem = AudioSystemAdapter.getDefaultAdapter();
+        this(broker, AudioSystemAdapter.getDefaultAdapter());
     }
 
     //-----------------------------------------------------------
     /** for mocking only, allows to inject AudioSystem adapter */
     /*package*/ AudioDeviceInventory(@NonNull AudioSystemAdapter audioSystem) {
-        mDeviceBroker = null;
-        mAudioSystem = audioSystem;
+        this(null, audioSystem);
     }
 
+    private AudioDeviceInventory(@Nullable AudioDeviceBroker broker,
+                       @Nullable AudioSystemAdapter audioSystem) {
+        mDeviceBroker = broker;
+        mAudioSystem = audioSystem;
+        mStrategies = AudioProductStrategy.getAudioProductStrategies();
+        mBluetoothDualModeEnabled = SystemProperties.getBoolean(
+                "persist.bluetooth.enable_dual_mode_audio", false);
+    }
     /*package*/ void setDeviceBroker(@NonNull AudioDeviceBroker broker) {
         mDeviceBroker = broker;
     }
@@ -203,8 +221,13 @@
         int mDeviceCodecFormat;
         final UUID mSensorUuid;
 
+        /** Disabled operating modes for this device. Use a negative logic so that by default
+         * an empty list means all modes are allowed.
+         * See BluetoothAdapter.AUDIO_MODE_DUPLEX and BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY */
+        @NonNull ArraySet<String> mDisabledModes = new ArraySet(0);
+
         DeviceInfo(int deviceType, String deviceName, String deviceAddress,
-                   int deviceCodecFormat, UUID sensorUuid) {
+                   int deviceCodecFormat, @Nullable UUID sensorUuid) {
             mDeviceType = deviceType;
             mDeviceName = deviceName == null ? "" : deviceName;
             mDeviceAddress = deviceAddress == null ? "" : deviceAddress;
@@ -212,11 +235,31 @@
             mSensorUuid = sensorUuid;
         }
 
+        void setModeDisabled(String mode) {
+            mDisabledModes.add(mode);
+        }
+        void setModeEnabled(String mode) {
+            mDisabledModes.remove(mode);
+        }
+        boolean isModeEnabled(String mode) {
+            return !mDisabledModes.contains(mode);
+        }
+        boolean isOutputOnlyModeEnabled() {
+            return isModeEnabled(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY);
+        }
+        boolean isDuplexModeEnabled() {
+            return isModeEnabled(BluetoothAdapter.AUDIO_MODE_DUPLEX);
+        }
+
         DeviceInfo(int deviceType, String deviceName, String deviceAddress,
                    int deviceCodecFormat) {
             this(deviceType, deviceName, deviceAddress, deviceCodecFormat, null);
         }
 
+        DeviceInfo(int deviceType, String deviceName, String deviceAddress) {
+            this(deviceType, deviceName, deviceAddress, AudioSystem.AUDIO_FORMAT_DEFAULT);
+        }
+
         @Override
         public String toString() {
             return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType)
@@ -224,7 +267,8 @@
                     + ") name:" + mDeviceName
                     + " addr:" + mDeviceAddress
                     + " codec: " + Integer.toHexString(mDeviceCodecFormat)
-                    + " sensorUuid: " + Objects.toString(mSensorUuid) + "]";
+                    + " sensorUuid: " + Objects.toString(mSensorUuid)
+                    + " disabled modes: " + mDisabledModes + "]";
         }
 
         @NonNull String getKey() {
@@ -276,9 +320,18 @@
             pw.println("  " + prefix + " type:0x" + Integer.toHexString(keyType)
                     + " (" + AudioSystem.getDeviceName(keyType)
                     + ") addr:" + valueAddress); });
+        pw.println("\n" + prefix + "Preferred devices for capture preset:");
         mPreferredDevicesForCapturePreset.forEach((capturePreset, devices) -> {
             pw.println("  " + prefix + "capturePreset:" + capturePreset
                     + " devices:" + devices); });
+        pw.println("\n" + prefix + "Applied devices roles for strategies:");
+        mAppliedStrategyRoles.forEach((key, devices) -> {
+            pw.println("  " + prefix + "strategy: " + key.first
+                    +  " role:" + key.second + " devices:" + devices); });
+        pw.println("\n" + prefix + "Applied devices roles for presets:");
+        mAppliedPresetRoles.forEach((key, devices) -> {
+            pw.println("  " + prefix + "preset: " + key.first
+                    +  " role:" + key.second + " devices:" + devices); });
     }
 
     //------------------------------------------------------------
@@ -299,15 +352,16 @@
                         AudioSystem.DEVICE_STATE_AVAILABLE,
                         di.mDeviceCodecFormat);
             }
+            mAppliedStrategyRoles.clear();
+            applyConnectedDevicesRoles_l();
         }
         synchronized (mPreferredDevices) {
             mPreferredDevices.forEach((strategy, devices) -> {
-                mAudioSystem.setDevicesRoleForStrategy(
-                        strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices); });
+                setPreferredDevicesForStrategy(strategy, devices); });
         }
         synchronized (mNonDefaultDevices) {
             mNonDefaultDevices.forEach((strategy, devices) -> {
-                mAudioSystem.setDevicesRoleForStrategy(
+                addDevicesRoleForStrategy(
                         strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices); });
         }
         synchronized (mPreferredDevicesForCapturePreset) {
@@ -380,8 +434,7 @@
                                     btInfo.mVolume * 10, btInfo.mAudioSystemDevice,
                                     "onSetBtActiveDevice");
                         }
-                        makeA2dpDeviceAvailable(address, BtHelper.getName(btInfo.mDevice),
-                                "onSetBtActiveDevice", btInfo.mCodec);
+                        makeA2dpDeviceAvailable(btInfo, "onSetBtActiveDevice");
                     }
                     break;
                 case BluetoothProfile.HEARING_AID:
@@ -397,10 +450,7 @@
                     if (switchToUnavailable) {
                         makeLeAudioDeviceUnavailableNow(address, btInfo.mAudioSystemDevice);
                     } else if (switchToAvailable) {
-                        makeLeAudioDeviceAvailable(address, BtHelper.getName(btInfo.mDevice),
-                                streamType, btInfo.mVolume == -1 ? -1 : btInfo.mVolume * 10,
-                                btInfo.mAudioSystemDevice,
-                                "onSetBtActiveDevice");
+                        makeLeAudioDeviceAvailable(btInfo, streamType, "onSetBtActiveDevice");
                     }
                     break;
                 default: throw new IllegalArgumentException("Invalid profile "
@@ -411,30 +461,30 @@
 
 
     @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
-        /*package*/ void onBluetoothA2dpDeviceConfigChange(
-            @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int event) {
+    /*package*/ void onBluetoothDeviceConfigChange(
+            @NonNull AudioDeviceBroker.BtDeviceInfo btInfo, int event) {
         MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId
-                + "onBluetoothA2dpDeviceConfigChange")
-                .set(MediaMetrics.Property.EVENT, BtHelper.a2dpDeviceEventToString(event));
+                + "onBluetoothDeviceConfigChange")
+                .set(MediaMetrics.Property.EVENT, BtHelper.deviceEventToString(event));
 
-        final BluetoothDevice btDevice = btInfo.getBtDevice();
+        final BluetoothDevice btDevice = btInfo.mDevice;
         if (btDevice == null) {
             mmi.set(MediaMetrics.Property.EARLY_RETURN, "btDevice null").record();
             return;
         }
         if (AudioService.DEBUG_DEVICES) {
-            Log.d(TAG, "onBluetoothA2dpDeviceConfigChange btDevice=" + btDevice);
+            Log.d(TAG, "onBluetoothDeviceConfigChange btDevice=" + btDevice);
         }
-        int a2dpVolume = btInfo.getVolume();
-        @AudioSystem.AudioFormatNativeEnumForBtCodec final int a2dpCodec = btInfo.getCodec();
+        int volume = btInfo.mVolume;
+        @AudioSystem.AudioFormatNativeEnumForBtCodec final int audioCodec = btInfo.mCodec;
 
         String address = btDevice.getAddress();
         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
             address = "";
         }
         AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
-                "onBluetoothA2dpDeviceConfigChange addr=" + address
-                    + " event=" + BtHelper.a2dpDeviceEventToString(event)));
+                "onBluetoothDeviceConfigChange addr=" + address
+                    + " event=" + BtHelper.deviceEventToString(event)));
 
         synchronized (mDevicesLock) {
             if (mDeviceBroker.hasScheduledA2dpConnection(btDevice)) {
@@ -449,53 +499,54 @@
                     AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
             final DeviceInfo di = mConnectedDevices.get(key);
             if (di == null) {
-                Log.e(TAG, "invalid null DeviceInfo in onBluetoothA2dpDeviceConfigChange");
+                Log.e(TAG, "invalid null DeviceInfo in onBluetoothDeviceConfigChange");
                 mmi.set(MediaMetrics.Property.EARLY_RETURN, "null DeviceInfo").record();
                 return;
             }
 
             mmi.set(MediaMetrics.Property.ADDRESS, address)
                     .set(MediaMetrics.Property.ENCODING,
-                            AudioSystem.audioFormatToString(a2dpCodec))
-                    .set(MediaMetrics.Property.INDEX, a2dpVolume)
+                            AudioSystem.audioFormatToString(audioCodec))
+                    .set(MediaMetrics.Property.INDEX, volume)
                     .set(MediaMetrics.Property.NAME, di.mDeviceName);
 
-            if (event == BtHelper.EVENT_ACTIVE_DEVICE_CHANGE) {
-                // Device is connected
-                if (a2dpVolume != -1) {
-                    mDeviceBroker.postSetVolumeIndexOnDevice(AudioSystem.STREAM_MUSIC,
-                            // convert index to internal representation in VolumeStreamState
-                            a2dpVolume * 10,
-                            AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
-                            "onBluetoothA2dpDeviceConfigChange");
-                }
-            } else if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) {
-                if (di.mDeviceCodecFormat != a2dpCodec) {
-                    di.mDeviceCodecFormat = a2dpCodec;
-                    mConnectedDevices.replace(key, di);
-                }
-            }
-            final int res = mAudioSystem.handleDeviceConfigChange(
-                    AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address,
-                    BtHelper.getName(btDevice), a2dpCodec);
 
-            if (res != AudioSystem.AUDIO_STATUS_OK) {
-                AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
-                        "APM handleDeviceConfigChange failed for A2DP device addr=" + address
-                                + " codec=" + AudioSystem.audioFormatToString(a2dpCodec))
-                        .printLog(TAG));
+            if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) {
+                boolean a2dpCodecChange = false;
+                if (btInfo.mProfile == BluetoothProfile.A2DP) {
+                    if (di.mDeviceCodecFormat != audioCodec) {
+                        di.mDeviceCodecFormat = audioCodec;
+                        mConnectedDevices.replace(key, di);
+                        a2dpCodecChange = true;
+                    }
+                    final int res = mAudioSystem.handleDeviceConfigChange(
+                            btInfo.mAudioSystemDevice, address,
+                            BtHelper.getName(btDevice), audioCodec);
 
-                int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
-                // force A2DP device disconnection in case of error so that AudioService state is
-                // consistent with audio policy manager state
-                setBluetoothActiveDevice(new AudioDeviceBroker.BtDeviceInfo(btDevice,
-                                BluetoothProfile.A2DP, BluetoothProfile.STATE_DISCONNECTED,
-                                musicDevice, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP));
-            } else {
-                AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
-                        "APM handleDeviceConfigChange success for A2DP device addr=" + address
-                                + " codec=" + AudioSystem.audioFormatToString(a2dpCodec))
-                        .printLog(TAG));
+                    if (res != AudioSystem.AUDIO_STATUS_OK) {
+                        AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+                                "APM handleDeviceConfigChange failed for A2DP device addr="
+                                        + address + " codec="
+                                        + AudioSystem.audioFormatToString(audioCodec))
+                                .printLog(TAG));
+
+                        // force A2DP device disconnection in case of error so that AudioService
+                        // state is consistent with audio policy manager state
+                        setBluetoothActiveDevice(new AudioDeviceBroker.BtDeviceInfo(btInfo,
+                                BluetoothProfile.STATE_DISCONNECTED));
+                    } else {
+                        AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+                                "APM handleDeviceConfigChange success for A2DP device addr="
+                                        + address
+                                        + " codec=" + AudioSystem.audioFormatToString(audioCodec))
+                                .printLog(TAG));
+
+                    }
+                }
+                if (!a2dpCodecChange) {
+                    updateBluetoothPreferredModes_l();
+                    mDeviceBroker.postNotifyPreferredAudioProfileApplied(btDevice);
+                }
             }
         }
         mmi.record();
@@ -578,7 +629,7 @@
             }
 
             if (!handleDeviceConnection(wdcs.mAttributes,
-                    wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED, wdcs.mForTest)) {
+                    wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED, wdcs.mForTest, null)) {
                 // change of connection state failed, bailout
                 mmi.set(MediaMetrics.Property.EARLY_RETURN, "change of connection state failed")
                         .record();
@@ -712,23 +763,35 @@
 
     /*package*/ int setPreferredDevicesForStrategySync(int strategy,
             @NonNull List<AudioDeviceAttributes> devices) {
-        int status = AudioSystem.ERROR;
-
-        try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
-            AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
-                            "setPreferredDevicesForStrategySync, strategy: " + strategy
-                            + " devices: " + devices)).printLog(TAG));
-            status = mAudioSystem.setDevicesRoleForStrategy(
-                    strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
-        }
-
+        final int status = setPreferredDevicesForStrategy(strategy, devices);
         if (status == AudioSystem.SUCCESS) {
             mDeviceBroker.postSaveSetPreferredDevicesForStrategy(strategy, devices);
         }
         return status;
     }
 
+    /*package*/ int setPreferredDevicesForStrategy(int strategy,
+            @NonNull List<AudioDeviceAttributes> devices) {
+        int status = AudioSystem.ERROR;
+        try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+            AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
+                    "setPreferredDevicesForStrategy, strategy: " + strategy
+                            + " devices: " + devices)).printLog(TAG));
+            status = setDevicesRoleForStrategy(
+                    strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
+        }
+        return status;
+    }
+
     /*package*/ int removePreferredDevicesForStrategySync(int strategy) {
+        final int status = removePreferredDevicesForStrategy(strategy);
+        if (status == AudioSystem.SUCCESS) {
+            mDeviceBroker.postSaveRemovePreferredDevicesForStrategy(strategy);
+        }
+        return status;
+    }
+
+    /*package*/ int removePreferredDevicesForStrategy(int strategy) {
         int status = AudioSystem.ERROR;
 
         try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
@@ -736,13 +799,9 @@
                             "removePreferredDevicesForStrategySync, strategy: "
                             + strategy)).printLog(TAG));
 
-            status = mAudioSystem.clearDevicesRoleForStrategy(
+            status = clearDevicesRoleForStrategy(
                     strategy, AudioSystem.DEVICE_ROLE_PREFERRED);
         }
-
-        if (status == AudioSystem.SUCCESS) {
-            mDeviceBroker.postSaveRemovePreferredDevicesForStrategy(strategy);
-        }
         return status;
     }
 
@@ -757,7 +816,7 @@
             AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
                             "setDeviceAsNonDefaultForStrategySync, strategy: " + strategy
                             + " device: " + device)).printLog(TAG));
-            status = mAudioSystem.setDevicesRoleForStrategy(
+            status = addDevicesRoleForStrategy(
                     strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices);
         }
 
@@ -779,7 +838,7 @@
                             "removeDeviceAsNonDefaultForStrategySync, strategy: "
                             + strategy + " devices: " + device)).printLog(TAG));
 
-            status = mAudioSystem.removeDevicesRoleForStrategy(
+            status = removeDevicesRoleForStrategy(
                     strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices);
         }
 
@@ -812,33 +871,70 @@
 
     /*package*/ int setPreferredDevicesForCapturePresetSync(
             int capturePreset, @NonNull List<AudioDeviceAttributes> devices) {
-        int status = AudioSystem.ERROR;
-
-        try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
-            status = mAudioSystem.setDevicesRoleForCapturePreset(
-                    capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
-        }
-
+        final int status = setPreferredDevicesForCapturePreset(capturePreset, devices);
         if (status == AudioSystem.SUCCESS) {
             mDeviceBroker.postSaveSetPreferredDevicesForCapturePreset(capturePreset, devices);
         }
         return status;
     }
 
-    /*package*/ int clearPreferredDevicesForCapturePresetSync(int capturePreset) {
-        int status  = AudioSystem.ERROR;
-
+    private int setPreferredDevicesForCapturePreset(
+            int capturePreset, @NonNull List<AudioDeviceAttributes> devices) {
+        int status = AudioSystem.ERROR;
         try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
-            status = mAudioSystem.clearDevicesRoleForCapturePreset(
-                    capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED);
+            status = setDevicesRoleForCapturePreset(
+                    capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
         }
+        return status;
+    }
 
+    /*package*/ int clearPreferredDevicesForCapturePresetSync(int capturePreset) {
+        final int status  = clearPreferredDevicesForCapturePreset(capturePreset);
         if (status == AudioSystem.SUCCESS) {
             mDeviceBroker.postSaveClearPreferredDevicesForCapturePreset(capturePreset);
         }
         return status;
     }
 
+    private int clearPreferredDevicesForCapturePreset(int capturePreset) {
+        int status  = AudioSystem.ERROR;
+
+        try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+            status = clearDevicesRoleForCapturePreset(
+                    capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED);
+        }
+        return status;
+    }
+
+    private int addDevicesRoleForCapturePreset(int capturePreset, int role,
+                                               @NonNull List<AudioDeviceAttributes> devices) {
+        return addDevicesRole(mAppliedPresetRoles, (p, r, d) -> {
+            return mAudioSystem.addDevicesRoleForCapturePreset(p, r, d);
+        }, capturePreset, role, devices);
+    }
+
+    private int removeDevicesRoleForCapturePreset(int capturePreset, int role,
+                                                  @NonNull List<AudioDeviceAttributes> devices) {
+        return removeDevicesRole(mAppliedPresetRoles, (p, r, d) -> {
+            return mAudioSystem.removeDevicesRoleForCapturePreset(p, r, d);
+        }, capturePreset, role, devices);
+    }
+
+    private int setDevicesRoleForCapturePreset(int capturePreset, int role,
+                                               @NonNull List<AudioDeviceAttributes> devices) {
+        return setDevicesRole(mAppliedPresetRoles, (p, r, d) -> {
+            return mAudioSystem.addDevicesRoleForCapturePreset(p, r, d);
+        }, (p, r, d) -> {
+                return mAudioSystem.clearDevicesRoleForCapturePreset(p, r);
+            }, capturePreset, role, devices);
+    }
+
+    private int clearDevicesRoleForCapturePreset(int capturePreset, int role) {
+        return clearDevicesRole(mAppliedPresetRoles, (p, r, d) -> {
+            return mAudioSystem.clearDevicesRoleForCapturePreset(p, r);
+        }, capturePreset, role);
+    }
+
     /*package*/ void registerCapturePresetDevicesRoleDispatcher(
             @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) {
         mDevRoleCapturePresetDispatchers.register(dispatcher);
@@ -849,7 +945,208 @@
         mDevRoleCapturePresetDispatchers.unregister(dispatcher);
     }
 
-    //-----------------------------------------------------------------------
+    private int addDevicesRoleForStrategy(int strategy, int role,
+                                          @NonNull List<AudioDeviceAttributes> devices) {
+        return addDevicesRole(mAppliedStrategyRoles, (s, r, d) -> {
+            return mAudioSystem.setDevicesRoleForStrategy(s, r, d);
+        }, strategy, role, devices);
+    }
+
+    private int removeDevicesRoleForStrategy(int strategy, int role,
+                                      @NonNull List<AudioDeviceAttributes> devices) {
+        return removeDevicesRole(mAppliedStrategyRoles, (s, r, d) -> {
+            return mAudioSystem.removeDevicesRoleForStrategy(s, r, d);
+        }, strategy, role, devices);
+    }
+
+    private int setDevicesRoleForStrategy(int strategy, int role,
+                                          @NonNull List<AudioDeviceAttributes> devices) {
+        return setDevicesRole(mAppliedStrategyRoles, (s, r, d) -> {
+            return mAudioSystem.setDevicesRoleForStrategy(s, r, d);
+        }, (s, r, d) -> {
+                return mAudioSystem.clearDevicesRoleForStrategy(s, r);
+            }, strategy, role, devices);
+    }
+
+    private int clearDevicesRoleForStrategy(int strategy, int role) {
+        return clearDevicesRole(mAppliedStrategyRoles, (s, r, d) -> {
+            return mAudioSystem.clearDevicesRoleForStrategy(s, r);
+        }, strategy, role);
+    }
+
+    //------------------------------------------------------------
+    // Cache for applied roles for strategies and devices. The cache avoids reapplying the
+    // same list of devices for a given role and strategy and the corresponding systematic
+    // redundant work in audio policy manager and audio flinger.
+    // The key is the pair <Strategy , Role> and the value is the current list of devices.
+
+    private final ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>>
+            mAppliedStrategyRoles = new ArrayMap<>();
+
+    // Cache for applied roles for capture presets and devices. The cache avoids reapplying the
+    // same list of devices for a given role and capture preset and the corresponding systematic
+    // redundant work in audio policy manager and audio flinger.
+    // The key is the pair <Preset , Role> and the value is the current list of devices.
+    private final ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>>
+            mAppliedPresetRoles = new ArrayMap<>();
+
+    interface AudioSystemInterface {
+        int deviceRoleAction(int usecase, int role, @Nullable List<AudioDeviceAttributes> devices);
+    }
+
+    private int addDevicesRole(
+            ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> rolesMap,
+            AudioSystemInterface asi,
+            int useCase, int role, @NonNull List<AudioDeviceAttributes> devices) {
+        synchronized (rolesMap) {
+            Pair<Integer, Integer> key = new Pair<>(useCase, role);
+            List<AudioDeviceAttributes> roleDevices = new ArrayList<>();
+            List<AudioDeviceAttributes> appliedDevices = new ArrayList<>();
+
+            if (rolesMap.containsKey(key)) {
+                roleDevices = rolesMap.get(key);
+                for (AudioDeviceAttributes device : devices) {
+                    if (!roleDevices.contains(device)) {
+                        appliedDevices.add(device);
+                    }
+                }
+            } else {
+                appliedDevices.addAll(devices);
+            }
+            if (appliedDevices.isEmpty()) {
+                return AudioSystem.SUCCESS;
+            }
+            final int status = asi.deviceRoleAction(useCase, role, appliedDevices);
+            if (status == AudioSystem.SUCCESS) {
+                roleDevices.addAll(appliedDevices);
+                rolesMap.put(key, roleDevices);
+            }
+            return status;
+        }
+    }
+
+    private int removeDevicesRole(
+            ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> rolesMap,
+            AudioSystemInterface asi,
+            int useCase, int role, @NonNull List<AudioDeviceAttributes> devices) {
+        synchronized (rolesMap) {
+            Pair<Integer, Integer> key = new Pair<>(useCase, role);
+            if (!rolesMap.containsKey(key)) {
+                return AudioSystem.SUCCESS;
+            }
+            List<AudioDeviceAttributes> roleDevices = rolesMap.get(key);
+            List<AudioDeviceAttributes> appliedDevices = new ArrayList<>();
+            for (AudioDeviceAttributes device : devices) {
+                if (roleDevices.contains(device)) {
+                    appliedDevices.add(device);
+                }
+            }
+            if (appliedDevices.isEmpty()) {
+                return AudioSystem.SUCCESS;
+            }
+            final int status = asi.deviceRoleAction(useCase, role, appliedDevices);
+            if (status == AudioSystem.SUCCESS) {
+                roleDevices.removeAll(appliedDevices);
+                if (roleDevices.isEmpty()) {
+                    rolesMap.remove(key);
+                } else {
+                    rolesMap.put(key, roleDevices);
+                }
+            }
+            return status;
+        }
+    }
+
+    private int setDevicesRole(
+            ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> rolesMap,
+            AudioSystemInterface addOp,
+            AudioSystemInterface clearOp,
+            int useCase, int role, @NonNull List<AudioDeviceAttributes> devices) {
+        synchronized (rolesMap) {
+            Pair<Integer, Integer> key = new Pair<>(useCase, role);
+            List<AudioDeviceAttributes> roleDevices = new ArrayList<>();
+            List<AudioDeviceAttributes> appliedDevices = new ArrayList<>();
+
+            if (rolesMap.containsKey(key)) {
+                roleDevices = rolesMap.get(key);
+                boolean equal = false;
+                if (roleDevices.size() == devices.size()) {
+                    roleDevices.retainAll(devices);
+                    equal = roleDevices.size() == devices.size();
+                }
+                if (!equal) {
+                    clearOp.deviceRoleAction(useCase, role, null);
+                    roleDevices.clear();
+                    appliedDevices.addAll(devices);
+                }
+            } else {
+                appliedDevices.addAll(devices);
+            }
+            if (appliedDevices.isEmpty()) {
+                return AudioSystem.SUCCESS;
+            }
+            final int status = addOp.deviceRoleAction(useCase, role, appliedDevices);
+            if (status == AudioSystem.SUCCESS) {
+                roleDevices.addAll(appliedDevices);
+                rolesMap.put(key, roleDevices);
+            }
+            return status;
+        }
+    }
+
+    private int clearDevicesRole(
+            ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> rolesMap,
+            AudioSystemInterface asi, int useCase, int role) {
+        synchronized (rolesMap) {
+            Pair<Integer, Integer> key = new Pair<>(useCase, role);
+            if (!rolesMap.containsKey(key)) {
+                return AudioSystem.SUCCESS;
+            }
+            final int status = asi.deviceRoleAction(useCase, role, null);
+            if (status == AudioSystem.SUCCESS) {
+                rolesMap.remove(key);
+            }
+            return status;
+        }
+    }
+
+    @GuardedBy("mDevicesLock")
+    private void purgeDevicesRoles_l() {
+        purgeRoles(mAppliedStrategyRoles, (s, r, d) -> {
+            return mAudioSystem.removeDevicesRoleForStrategy(s, r, d); });
+        purgeRoles(mAppliedPresetRoles, (p, r, d) -> {
+            return mAudioSystem.removeDevicesRoleForCapturePreset(p, r, d); });
+    }
+
+    @GuardedBy("mDevicesLock")
+    private void purgeRoles(
+            ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> rolesMap,
+            AudioSystemInterface asi) {
+        synchronized (rolesMap) {
+            Iterator<Map.Entry<Pair<Integer, Integer>, List<AudioDeviceAttributes>>> itRole =
+                    rolesMap.entrySet().iterator();
+            while (itRole.hasNext()) {
+                Map.Entry<Pair<Integer, Integer>, List<AudioDeviceAttributes>> entry =
+                        itRole.next();
+                Pair<Integer, Integer> keyRole = entry.getKey();
+                Iterator<AudioDeviceAttributes> itDev = rolesMap.get(keyRole).iterator();
+                while (itDev.hasNext()) {
+                    AudioDeviceAttributes ada = itDev.next();
+                    final String devKey = DeviceInfo.makeDeviceListKey(ada.getInternalType(),
+                            ada.getAddress());
+                    if (mConnectedDevices.get(devKey) == null) {
+                        asi.deviceRoleAction(keyRole.first, keyRole.second, Arrays.asList(ada));
+                        itDev.remove();
+                    }
+                }
+                if (rolesMap.get(keyRole).isEmpty()) {
+                    itRole.remove();
+                }
+            }
+        }
+    }
+
+//-----------------------------------------------------------------------
 
     /**
      * Check if a device is in the list of connected devices
@@ -871,10 +1168,11 @@
      * @param connect true if connection
      * @param isForTesting if true, not calling AudioSystem for the connection as this is
      *                    just for testing
+     * @param btDevice the corresponding Bluetooth device when relevant.
      * @return false if an error was reported by AudioSystem
      */
     /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes, boolean connect,
-            boolean isForTesting) {
+            boolean isForTesting, @Nullable BluetoothDevice btDevice) {
         int device = attributes.getInternalType();
         String address = attributes.getAddress();
         String deviceName = attributes.getName();
@@ -889,6 +1187,7 @@
                 .set(MediaMetrics.Property.MODE, connect
                         ? MediaMetrics.Value.CONNECT : MediaMetrics.Value.DISCONNECT)
                 .set(MediaMetrics.Property.NAME, deviceName);
+        boolean status = false;
         synchronized (mDevicesLock) {
             final String deviceKey = DeviceInfo.makeDeviceListKey(device, address);
             if (AudioService.DEBUG_DEVICES) {
@@ -916,24 +1215,33 @@
                             .record();
                     return false;
                 }
-                mConnectedDevices.put(deviceKey, new DeviceInfo(
-                        device, deviceName, address, AudioSystem.AUDIO_FORMAT_DEFAULT));
+                mConnectedDevices.put(deviceKey, new DeviceInfo(device, deviceName, address));
                 mDeviceBroker.postAccessoryPlugMediaUnmute(device);
-                mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record();
-                return true;
+                status = true;
             } else if (!connect && isConnected) {
                 mAudioSystem.setDeviceConnectionState(attributes,
                         AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT);
                 // always remove even if disconnection failed
                 mConnectedDevices.remove(deviceKey);
-                mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record();
-                return true;
+                status = true;
             }
-            Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey
-                    + ", deviceSpec=" + di + ", connect=" + connect);
+            if (status) {
+                if (AudioSystem.isBluetoothScoDevice(device)) {
+                    updateBluetoothPreferredModes_l();
+                    if (connect) {
+                        mDeviceBroker.postNotifyPreferredAudioProfileApplied(btDevice);
+                    } else {
+                        purgeDevicesRoles_l();
+                    }
+                }
+                mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record();
+            } else {
+                Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey
+                        + ", deviceSpec=" + di + ", connect=" + connect);
+                mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.DISCONNECTED).record();
+            }
         }
-        mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.DISCONNECTED).record();
-        return false;
+        return status;
     }
 
 
@@ -1142,15 +1450,20 @@
     // Internal utilities
 
     @GuardedBy("mDevicesLock")
-    private void makeA2dpDeviceAvailable(String address, String name, String eventSource,
-            int a2dpCodec) {
+    private void makeA2dpDeviceAvailable(AudioDeviceBroker.BtDeviceInfo btInfo,
+                                         String eventSource) {
+        final String address = btInfo.mDevice.getAddress();
+        final String name = BtHelper.getName(btInfo.mDevice);
+        final int a2dpCodec = btInfo.mCodec;
+
         // enable A2DP before notifying A2DP connection to avoid unnecessary processing in
         // audio policy manager
         mDeviceBroker.setBluetoothA2dpOnInt(true, true /*fromA2dp*/, eventSource);
         // at this point there could be another A2DP device already connected in APM, but it
         // doesn't matter as this new one will overwrite the previous one
-        final int res = mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
-                AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, name),
+        AudioDeviceAttributes ada = new AudioDeviceAttributes(
+                AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, name);
+        final int res = mAudioSystem.setDeviceConnectionState(ada,
                 AudioSystem.DEVICE_STATE_AVAILABLE, a2dpCodec);
 
         // TODO: log in MediaMetrics once distinction between connection failure and
@@ -1167,13 +1480,12 @@
         }
 
         // Reset A2DP suspend state each time a new sink is connected
-        mAudioSystem.setParameters("A2dpSuspended=false");
+        mDeviceBroker.clearA2dpSuspended();
 
         // The convention for head tracking sensors associated with A2DP devices is to
         // use a UUID derived from the MAC address as follows:
         //   time_low = 0, time_mid = 0, time_hi = 0, clock_seq = 0, node = MAC Address
-        UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(
-                new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address));
+        UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(ada);
         final DeviceInfo di = new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
                 address, a2dpCodec, sensorUuid);
         final String diKey = di.getKey();
@@ -1184,6 +1496,206 @@
 
         mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
         setCurrentAudioRouteNameIfPossible(name, true /*fromA2dp*/);
+
+        updateBluetoothPreferredModes_l();
+        mDeviceBroker.postNotifyPreferredAudioProfileApplied(btInfo.mDevice);
+    }
+
+    static final int[] CAPTURE_PRESETS = new int[] {AudioSource.MIC, AudioSource.CAMCORDER,
+            AudioSource.VOICE_RECOGNITION, AudioSource.VOICE_COMMUNICATION,
+            AudioSource.UNPROCESSED, AudioSource.VOICE_PERFORMANCE, AudioSource.HOTWORD};
+
+    // reflects system property persist.bluetooth.enable_dual_mode_audio
+    final boolean mBluetoothDualModeEnabled;
+    /**
+     * Goes over all connected Bluetooth devices and set the audio policy device role to DISABLED
+     * or not according to their own and other devices modes.
+     * The top priority is given to LE devices, then SCO ,then A2DP.
+     */
+    @GuardedBy("mDevicesLock")
+    private void applyConnectedDevicesRoles_l() {
+        if (!mBluetoothDualModeEnabled) {
+            return;
+        }
+        DeviceInfo leOutDevice =
+                getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_OUT_ALL_BLE_SET);
+        DeviceInfo leInDevice =
+                getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_IN_ALL_BLE_SET);
+        DeviceInfo a2dpDevice =
+                getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_OUT_ALL_A2DP_SET);
+        DeviceInfo scoOutDevice =
+                getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_OUT_ALL_SCO_SET);
+        DeviceInfo scoInDevice =
+                getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_IN_ALL_SCO_SET);
+        boolean disableA2dp = (leOutDevice != null && leOutDevice.isOutputOnlyModeEnabled());
+        boolean disableSco = (leOutDevice != null && leOutDevice.isDuplexModeEnabled())
+                || (leInDevice != null && leInDevice.isDuplexModeEnabled());
+        AudioDeviceAttributes communicationDevice =
+                mDeviceBroker.mActiveCommunicationDevice == null
+                        ? null : ((mDeviceBroker.isInCommunication()
+                                    && mDeviceBroker.mActiveCommunicationDevice != null)
+                            ? new AudioDeviceAttributes(mDeviceBroker.mActiveCommunicationDevice)
+                            : null);
+
+        if (AudioService.DEBUG_DEVICES) {
+            Log.i(TAG, "applyConnectedDevicesRoles_l\n - leOutDevice: " + leOutDevice
+                    + "\n - leInDevice: " + leInDevice
+                    + "\n - a2dpDevice: " + a2dpDevice
+                    + "\n - scoOutDevice: " + scoOutDevice
+                    + "\n - scoInDevice: " + scoInDevice
+                    + "\n - disableA2dp: " + disableA2dp
+                    + ", disableSco: " + disableSco);
+        }
+
+        for (DeviceInfo di : mConnectedDevices.values()) {
+            if (!AudioSystem.isBluetoothDevice(di.mDeviceType)) {
+                continue;
+            }
+            AudioDeviceAttributes ada =
+                    new AudioDeviceAttributes(di.mDeviceType, di.mDeviceAddress, di.mDeviceName);
+            if (AudioService.DEBUG_DEVICES) {
+                Log.i(TAG, "  + checking Device: " + ada);
+            }
+            if (ada.equalTypeAddress(communicationDevice)) {
+                continue;
+            }
+
+            if (AudioSystem.isBluetoothOutDevice(di.mDeviceType)) {
+                for (AudioProductStrategy strategy : mStrategies) {
+                    boolean disable = false;
+                    if (strategy.getId() == mDeviceBroker.mCommunicationStrategyId) {
+                        if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) {
+                            disable = disableSco || !di.isDuplexModeEnabled();
+                        } else if (AudioSystem.isBluetoothLeDevice(di.mDeviceType)) {
+                            disable = !di.isDuplexModeEnabled();
+                        }
+                    } else {
+                        if (AudioSystem.isBluetoothA2dpOutDevice(di.mDeviceType)) {
+                            disable = disableA2dp || !di.isOutputOnlyModeEnabled();
+                        } else if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) {
+                            disable = disableSco || !di.isOutputOnlyModeEnabled();
+                        } else if (AudioSystem.isBluetoothLeDevice(di.mDeviceType)) {
+                            disable = !di.isOutputOnlyModeEnabled();
+                        }
+                    }
+                    if (AudioService.DEBUG_DEVICES) {
+                        Log.i(TAG, "     - strategy: " + strategy.getId()
+                                + ", disable: " + disable);
+                    }
+                    if (disable) {
+                        addDevicesRoleForStrategy(strategy.getId(),
+                                AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada));
+                    } else {
+                        removeDevicesRoleForStrategy(strategy.getId(),
+                                AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada));
+                    }
+                }
+            }
+            if (AudioSystem.isBluetoothInDevice(di.mDeviceType)) {
+                for (int capturePreset : CAPTURE_PRESETS) {
+                    boolean disable = false;
+                    if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) {
+                        disable = disableSco || !di.isDuplexModeEnabled();
+                    } else if (AudioSystem.isBluetoothLeDevice(di.mDeviceType)) {
+                        disable = !di.isDuplexModeEnabled();
+                    }
+                    if (AudioService.DEBUG_DEVICES) {
+                        Log.i(TAG, "      - capturePreset: " + capturePreset
+                                + ", disable: " + disable);
+                    }
+                    if (disable) {
+                        addDevicesRoleForCapturePreset(capturePreset,
+                                AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada));
+                    } else {
+                        removeDevicesRoleForCapturePreset(capturePreset,
+                                AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada));
+                    }
+                }
+            }
+        }
+    }
+
+    /* package */ void applyConnectedDevicesRoles() {
+        synchronized (mDevicesLock) {
+            applyConnectedDevicesRoles_l();
+        }
+    }
+
+    @GuardedBy("mDevicesLock")
+    int checkProfileIsConnected(int profile) {
+        switch (profile) {
+            case BluetoothProfile.HEADSET:
+                if (getFirstConnectedDeviceOfTypes(
+                        AudioSystem.DEVICE_OUT_ALL_SCO_SET) != null
+                        || getFirstConnectedDeviceOfTypes(
+                                AudioSystem.DEVICE_IN_ALL_SCO_SET) != null) {
+                    return profile;
+                }
+                break;
+            case BluetoothProfile.A2DP:
+                if (getFirstConnectedDeviceOfTypes(
+                        AudioSystem.DEVICE_OUT_ALL_A2DP_SET) != null) {
+                    return profile;
+                }
+                break;
+            case BluetoothProfile.LE_AUDIO:
+            case BluetoothProfile.LE_AUDIO_BROADCAST:
+                if (getFirstConnectedDeviceOfTypes(
+                        AudioSystem.DEVICE_OUT_ALL_BLE_SET) != null
+                        || getFirstConnectedDeviceOfTypes(
+                                AudioSystem.DEVICE_IN_ALL_BLE_SET) != null) {
+                    return profile;
+                }
+                break;
+            default:
+                break;
+        }
+        return 0;
+    }
+
+    @GuardedBy("mDevicesLock")
+    private void updateBluetoothPreferredModes_l() {
+        if (!mBluetoothDualModeEnabled) {
+            return;
+        }
+        HashSet<String> processedAddresses = new HashSet<>(0);
+        for (DeviceInfo di : mConnectedDevices.values()) {
+            if (!AudioSystem.isBluetoothDevice(di.mDeviceType)
+                    || processedAddresses.contains(di.mDeviceAddress)) {
+                continue;
+            }
+            Bundle preferredProfiles = BtHelper.getPreferredAudioProfiles(di.mDeviceAddress);
+            if (AudioService.DEBUG_DEVICES) {
+                Log.i(TAG, "updateBluetoothPreferredModes_l processing device address: "
+                        + di.mDeviceAddress + ", preferredProfiles: " + preferredProfiles);
+            }
+            for (DeviceInfo di2 : mConnectedDevices.values()) {
+                if (!AudioSystem.isBluetoothDevice(di2.mDeviceType)
+                        || !di.mDeviceAddress.equals(di2.mDeviceAddress)) {
+                    continue;
+                }
+                int profile = BtHelper.getProfileFromType(di2.mDeviceType);
+                if (profile == 0) {
+                    continue;
+                }
+                int preferredProfile = checkProfileIsConnected(
+                        preferredProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX));
+                if (preferredProfile == profile || preferredProfile == 0) {
+                    di2.setModeEnabled(BluetoothAdapter.AUDIO_MODE_DUPLEX);
+                } else {
+                    di2.setModeDisabled(BluetoothAdapter.AUDIO_MODE_DUPLEX);
+                }
+                preferredProfile = checkProfileIsConnected(
+                        preferredProfiles.getInt(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY));
+                if (preferredProfile == profile || preferredProfile == 0) {
+                    di2.setModeEnabled(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY);
+                } else {
+                    di2.setModeDisabled(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY);
+                }
+            }
+            processedAddresses.add(di.mDeviceAddress);
+        }
+        applyConnectedDevicesRoles_l();
     }
 
     @GuardedBy("mDevicesLock")
@@ -1231,13 +1743,17 @@
         // Remove A2DP routes as well
         setCurrentAudioRouteNameIfPossible(null, true /*fromA2dp*/);
         mmi.record();
+
+        updateBluetoothPreferredModes_l();
+        purgeDevicesRoles_l();
     }
 
     @GuardedBy("mDevicesLock")
     private void makeA2dpDeviceUnavailableLater(String address, int delayMs) {
         // prevent any activity on the A2DP audio output to avoid unwanted
         // reconnection of the sink.
-        mAudioSystem.setParameters("A2dpSuspended=true");
+        mDeviceBroker.setA2dpSuspended(
+                true /*enable*/, true /*internal*/, "makeA2dpDeviceUnavailableLater");
         // retrieve DeviceInfo before removing device
         final String deviceKey =
                 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
@@ -1259,8 +1775,7 @@
                 AudioSystem.AUDIO_FORMAT_DEFAULT);
         mConnectedDevices.put(
                 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
-                new DeviceInfo(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "",
-                        address, AudioSystem.AUDIO_FORMAT_DEFAULT));
+                new DeviceInfo(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "", address));
     }
 
     @GuardedBy("mDevicesLock")
@@ -1286,8 +1801,7 @@
                 AudioSystem.AUDIO_FORMAT_DEFAULT);
         mConnectedDevices.put(
                 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address),
-                new DeviceInfo(AudioSystem.DEVICE_OUT_HEARING_AID, name,
-                        address, AudioSystem.AUDIO_FORMAT_DEFAULT));
+                new DeviceInfo(AudioSystem.DEVICE_OUT_HEARING_AID, name, address));
         mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_HEARING_AID);
         mDeviceBroker.postApplyVolumeOnDevice(streamType,
                 AudioSystem.DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable");
@@ -1325,29 +1839,56 @@
      * @return true if a DEVICE_OUT_HEARING_AID is connected, false otherwise.
      */
     boolean isHearingAidConnected() {
+        return getFirstConnectedDeviceOfTypes(
+                Sets.newHashSet(AudioSystem.DEVICE_OUT_HEARING_AID)) != null;
+    }
+
+    /**
+     * Returns a DeviceInfo for the fist connected device matching one of the supplied types
+     */
+    private DeviceInfo getFirstConnectedDeviceOfTypes(Set<Integer> internalTypes) {
+        List<DeviceInfo> devices = getConnectedDevicesOfTypes(internalTypes);
+        return devices.isEmpty() ? null : devices.get(0);
+    }
+
+    /**
+     * Returns a list of connected devices matching one one of the supplied types
+     */
+    private List<DeviceInfo> getConnectedDevicesOfTypes(Set<Integer> internalTypes) {
+        ArrayList<DeviceInfo> devices = new ArrayList<>();
         synchronized (mDevicesLock) {
             for (DeviceInfo di : mConnectedDevices.values()) {
-                if (di.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) {
-                    return true;
+                if (internalTypes.contains(di.mDeviceType)) {
+                    devices.add(di);
                 }
             }
-            return false;
         }
+        return devices;
+    }
+
+    /* package */ AudioDeviceAttributes getDeviceOfType(int type) {
+        DeviceInfo di = getFirstConnectedDeviceOfTypes(Sets.newHashSet(type));
+        return di == null ? null : new AudioDeviceAttributes(
+                    di.mDeviceType, di.mDeviceAddress, di.mDeviceName);
     }
 
     @GuardedBy("mDevicesLock")
-    private void makeLeAudioDeviceAvailable(String address, String name, int streamType,
-            int volumeIndex, int device, String eventSource) {
+    private void makeLeAudioDeviceAvailable(
+            AudioDeviceBroker.BtDeviceInfo btInfo, int streamType, String eventSource) {
+        final String address = btInfo.mDevice.getAddress();
+        final String name = BtHelper.getName(btInfo.mDevice);
+        final int volumeIndex = btInfo.mVolume == -1 ? -1 : btInfo.mVolume * 10;
+        final int device = btInfo.mAudioSystemDevice;
+
         if (device != AudioSystem.DEVICE_NONE) {
             /* Audio Policy sees Le Audio similar to A2DP. Let's make sure
              * AUDIO_POLICY_FORCE_NO_BT_A2DP is not set
              */
             mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/, eventSource);
 
-            final int res = AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
-                    device, address, name),
-                    AudioSystem.DEVICE_STATE_AVAILABLE,
-                    AudioSystem.AUDIO_FORMAT_DEFAULT);
+            AudioDeviceAttributes ada = new AudioDeviceAttributes(device, address, name);
+            final int res = AudioSystem.setDeviceConnectionState(ada,
+                    AudioSystem.DEVICE_STATE_AVAILABLE,  AudioSystem.AUDIO_FORMAT_DEFAULT);
             if (res != AudioSystem.AUDIO_STATUS_OK) {
                 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                         "APM failed to make available LE Audio device addr=" + address
@@ -1358,12 +1899,13 @@
                 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                         "LE Audio device addr=" + address + " now available").printLog(TAG));
             }
-
             // Reset LEA suspend state each time a new sink is connected
-            mAudioSystem.setParameters("LeAudioSuspended=false");
+            mDeviceBroker.clearLeAudioSuspended();
 
+            UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(ada);
             mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address),
-                    new DeviceInfo(device, name, address, AudioSystem.AUDIO_FORMAT_DEFAULT));
+                    new DeviceInfo(device, name, address, AudioSystem.AUDIO_FORMAT_DEFAULT,
+                            sensorUuid));
             mDeviceBroker.postAccessoryPlugMediaUnmute(device);
             setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false);
         }
@@ -1379,6 +1921,9 @@
         final int maxIndex = mDeviceBroker.getMaxVssVolumeForStream(streamType);
         mDeviceBroker.postSetLeAudioVolumeIndex(leAudioVolIndex, maxIndex, streamType);
         mDeviceBroker.postApplyVolumeOnDevice(streamType, device, "makeLeAudioDeviceAvailable");
+
+        updateBluetoothPreferredModes_l();
+        mDeviceBroker.postNotifyPreferredAudioProfileApplied(btInfo.mDevice);
     }
 
     @GuardedBy("mDevicesLock")
@@ -1403,13 +1948,17 @@
         }
 
         setCurrentAudioRouteNameIfPossible(null, false /*fromA2dp*/);
+
+        updateBluetoothPreferredModes_l();
+        purgeDevicesRoles_l();
     }
 
     @GuardedBy("mDevicesLock")
     private void makeLeAudioDeviceUnavailableLater(String address, int device, int delayMs) {
         // prevent any activity on the LEA output to avoid unwanted
         // reconnection of the sink.
-        mAudioSystem.setParameters("LeAudioSuspended=true");
+        mDeviceBroker.setLeAudioSuspended(
+                true /*enable*/, true /*internal*/, "makeLeAudioDeviceUnavailableLater");
         // the device will be made unavailable later, so consider it disconnected right away
         mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address));
         // send the delayed message to make the device unavailable later
@@ -1737,18 +2286,6 @@
         }
     }
 
-    /* package */ AudioDeviceAttributes getDeviceOfType(int type) {
-        synchronized (mDevicesLock) {
-            for (DeviceInfo di : mConnectedDevices.values()) {
-                if (di.mDeviceType == type) {
-                    return new AudioDeviceAttributes(
-                            di.mDeviceType, di.mDeviceAddress, di.mDeviceName);
-                }
-            }
-        }
-        return null;
-    }
-
     //----------------------------------------------------------
     // For tests only
 
@@ -1759,10 +2296,12 @@
      */
     @VisibleForTesting
     public boolean isA2dpDeviceConnected(@NonNull BluetoothDevice device) {
-        final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
-                device.getAddress());
-        synchronized (mDevicesLock) {
-            return (mConnectedDevices.get(key) != null);
+        for (DeviceInfo di : getConnectedDevicesOfTypes(
+                Sets.newHashSet(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP))) {
+            if (di.mDeviceAddress.equals(device.getAddress())) {
+                return true;
+            }
         }
+        return false;
     }
 }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index ac55f28..a3163e0 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -6376,6 +6376,26 @@
         mDeviceBroker.setBluetoothScoOn(on, eventSource);
     }
 
+    /** @see AudioManager#setA2dpSuspended(boolean) */
+    @android.annotation.EnforcePermission(android.Manifest.permission.BLUETOOTH_STACK)
+    public void setA2dpSuspended(boolean enable) {
+        super.setA2dpSuspended_enforcePermission();
+        final String eventSource = new StringBuilder("setA2dpSuspended(").append(enable)
+                .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
+                .append(Binder.getCallingPid()).toString();
+        mDeviceBroker.setA2dpSuspended(enable, false /*internal*/, eventSource);
+    }
+
+    /** @see AudioManager#setA2dpSuspended(boolean) */
+    @android.annotation.EnforcePermission(android.Manifest.permission.BLUETOOTH_STACK)
+    public void setLeAudioSuspended(boolean enable) {
+        super.setLeAudioSuspended_enforcePermission();
+        final String eventSource = new StringBuilder("setLeAudioSuspended(").append(enable)
+                .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
+                .append(Binder.getCallingPid()).toString();
+        mDeviceBroker.setLeAudioSuspended(enable, false /*internal*/, eventSource);
+    }
+
     /** @see AudioManager#isBluetoothScoOn()
      * Note that it doesn't report internal state, but state seen by apps (which may have
      * called setBluetoothScoOn() */
@@ -7053,13 +7073,16 @@
             return deviceSet.iterator().next();
         } else {
             // Multiple device selection is either:
+            //  - dock + one other device: give priority to dock in this case.
             //  - speaker + one other device: give priority to speaker in this case.
             //  - one A2DP device + another device: happens with duplicated output. In this case
             // retain the device on the A2DP output as the other must not correspond to an active
             // selection if not the speaker.
             //  - HDMI-CEC system audio mode only output: give priority to available item in order.
 
-            if (deviceSet.contains(AudioSystem.DEVICE_OUT_SPEAKER)) {
+            if (deviceSet.contains(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET)) {
+                return AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET;
+            } else if (deviceSet.contains(AudioSystem.DEVICE_OUT_SPEAKER)) {
                 return AudioSystem.DEVICE_OUT_SPEAKER;
             } else if (deviceSet.contains(AudioSystem.DEVICE_OUT_SPEAKER_SAFE)) {
                 // Note: DEVICE_OUT_SPEAKER_SAFE not present in getDeviceSetForStreamDirect
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 7af7ed5..1142a8d 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -435,7 +435,7 @@
     }
 
     /**
-     * Same as {@link AudioSystem#removeDevicesRoleForCapturePreset(int, int, int[], String[])}
+     * Same as {@link AudioSystem#removeDevicesRoleForCapturePreset(int, int, List)}
      * @param capturePreset
      * @param role
      * @param devicesToRemove
@@ -448,6 +448,19 @@
     }
 
     /**
+     * Same as {@link AudioSystem#addDevicesRoleForCapturePreset(int, int, List)}
+     * @param capturePreset the capture preset to configure
+     * @param role the role of the devices
+     * @param devices the list of devices to be added as role for the given capture preset
+     * @return {@link #SUCCESS} if successfully add
+     */
+    public int addDevicesRoleForCapturePreset(
+            int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devices) {
+        invalidateRoutingCache();
+        return AudioSystem.addDevicesRoleForCapturePreset(capturePreset, role, devices);
+    }
+
+    /**
      * Same as {@link AudioSystem#}
      * @param capturePreset
      * @param role
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 631d7f5..e46c3cc 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -33,6 +33,7 @@
 import android.media.AudioSystem;
 import android.media.BluetoothProfileConnectionInfo;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.TextUtils;
@@ -150,60 +151,12 @@
         }
     }
 
-    //----------------------------------------------------------------------
-    /*package*/ static class BluetoothA2dpDeviceInfo {
-        private final @NonNull BluetoothDevice mBtDevice;
-        private final int mVolume;
-        private final @AudioSystem.AudioFormatNativeEnumForBtCodec int mCodec;
-
-        BluetoothA2dpDeviceInfo(@NonNull BluetoothDevice btDevice) {
-            this(btDevice, -1, AudioSystem.AUDIO_FORMAT_DEFAULT);
-        }
-
-        BluetoothA2dpDeviceInfo(@NonNull BluetoothDevice btDevice, int volume, int codec) {
-            mBtDevice = btDevice;
-            mVolume = volume;
-            mCodec = codec;
-        }
-
-        public @NonNull BluetoothDevice getBtDevice() {
-            return mBtDevice;
-        }
-
-        public int getVolume() {
-            return mVolume;
-        }
-
-        public @AudioSystem.AudioFormatNativeEnumForBtCodec int getCodec() {
-            return mCodec;
-        }
-
-        // redefine equality op so we can match messages intended for this device
-        @Override
-        public boolean equals(Object o) {
-            if (o == null) {
-                return false;
-            }
-            if (this == o) {
-                return true;
-            }
-            if (o instanceof BluetoothA2dpDeviceInfo) {
-                return mBtDevice.equals(((BluetoothA2dpDeviceInfo) o).getBtDevice());
-            }
-            return false;
-        }
-
-
-    }
-
     // A2DP device events
     /*package*/ static final int EVENT_DEVICE_CONFIG_CHANGE = 0;
-    /*package*/ static final int EVENT_ACTIVE_DEVICE_CHANGE = 1;
 
-    /*package*/ static String a2dpDeviceEventToString(int event) {
+    /*package*/ static String deviceEventToString(int event) {
         switch (event) {
             case EVENT_DEVICE_CONFIG_CHANGE: return "DEVICE_CONFIG_CHANGE";
-            case EVENT_ACTIVE_DEVICE_CHANGE: return "ACTIVE_DEVICE_CHANGE";
             default:
                 return new String("invalid event:" + event);
         }
@@ -492,8 +445,8 @@
     /*package*/ synchronized void resetBluetoothSco() {
         mScoAudioState = SCO_STATE_INACTIVE;
         broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
-        AudioSystem.setParameters("A2dpSuspended=false");
-        AudioSystem.setParameters("LeAudioSuspended=false");
+        mDeviceBroker.clearA2dpSuspended();
+        mDeviceBroker.clearLeAudioSuspended();
         mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco");
     }
 
@@ -620,11 +573,12 @@
         return btHeadsetDeviceToAudioDevice(mBluetoothHeadsetDevice);
     }
 
-    private AudioDeviceAttributes btHeadsetDeviceToAudioDevice(BluetoothDevice btDevice) {
+    private static AudioDeviceAttributes btHeadsetDeviceToAudioDevice(BluetoothDevice btDevice) {
         if (btDevice == null) {
             return new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, "");
         }
         String address = btDevice.getAddress();
+        String name = getName(btDevice);
         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
             address = "";
         }
@@ -646,7 +600,7 @@
                     + " btClass: " + (btClass == null ? "Unknown" : btClass)
                     + " nativeType: " + nativeType + " address: " + address);
         }
-        return new AudioDeviceAttributes(nativeType, address);
+        return new AudioDeviceAttributes(nativeType, address, name);
     }
 
     private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) {
@@ -655,12 +609,9 @@
         }
         int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET;
         AudioDeviceAttributes audioDevice =  btHeadsetDeviceToAudioDevice(btDevice);
-        String btDeviceName =  getName(btDevice);
         boolean result = false;
         if (isActive) {
-            result |= mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes(
-                    audioDevice.getInternalType(), audioDevice.getAddress(), btDeviceName),
-                    isActive);
+            result |= mDeviceBroker.handleDeviceConnection(audioDevice, isActive, btDevice);
         } else {
             int[] outDeviceTypes = {
                     AudioSystem.DEVICE_OUT_BLUETOOTH_SCO,
@@ -669,14 +620,14 @@
             };
             for (int outDeviceType : outDeviceTypes) {
                 result |= mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes(
-                        outDeviceType, audioDevice.getAddress(), btDeviceName),
-                        isActive);
+                        outDeviceType, audioDevice.getAddress(), audioDevice.getName()),
+                        isActive, btDevice);
             }
         }
         // handleDeviceConnection() && result to make sure the method get executed
         result = mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes(
-                        inDevice, audioDevice.getAddress(), btDeviceName),
-                isActive) && result;
+                        inDevice, audioDevice.getAddress(), audioDevice.getName()),
+                isActive, btDevice) && result;
         return result;
     }
 
@@ -973,6 +924,30 @@
         }
     }
 
+    /*package */ static int getProfileFromType(int deviceType) {
+        if (AudioSystem.isBluetoothA2dpOutDevice(deviceType)) {
+            return BluetoothProfile.A2DP;
+        } else if (AudioSystem.isBluetoothScoDevice(deviceType)) {
+            return BluetoothProfile.HEADSET;
+        } else if (AudioSystem.isBluetoothLeDevice(deviceType)) {
+            return BluetoothProfile.LE_AUDIO;
+        }
+        return 0; // 0 is not a valid profile
+    }
+
+    /*package */ static Bundle getPreferredAudioProfiles(String address) {
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        return adapter.getPreferredAudioProfiles(adapter.getRemoteDevice(address));
+    }
+
+    /**
+     * Notifies Bluetooth framework that new preferred audio profiles for Bluetooth devices
+     * have been applied.
+     */
+    public static void onNotifyPreferredAudioProfileApplied(BluetoothDevice btDevice) {
+        BluetoothAdapter.getDefaultAdapter().notifyActiveDeviceChangeApplied(btDevice);
+    }
+
     /**
      * Returns the string equivalent for the btDeviceClass class.
      */
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index 7c8e6df..5127d26 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -19,6 +19,8 @@
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR_BASE;
 import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT;
 import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE;
 
@@ -519,6 +521,9 @@
 
         try {
             mStatusBarService.onBiometricHelp(sensorIdToModality(sensorId), message);
+            final int aAcquiredInfo = acquiredInfo == FINGERPRINT_ACQUIRED_VENDOR
+                    ? (vendorCode + FINGERPRINT_ACQUIRED_VENDOR_BASE) : acquiredInfo;
+            mClientReceiver.onAcquired(aAcquiredInfo, message);
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception", e);
         }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index 7ae31b2..50d375c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -212,6 +212,8 @@
         // 1) Authenticated == true
         // 2) Error occurred
         // 3) Authenticated == false
+        // 4) onLockout
+        // 5) onLockoutTimed
         mCallback.onClientFinished(this, true /* success */);
     }
 
@@ -304,11 +306,7 @@
         PerformanceTracker.getInstanceForSensorId(getSensorId())
                 .incrementTimedLockoutForUser(getTargetUserId());
 
-        try {
-            getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception", e);
-        }
+        onError(error, 0 /* vendorCode */);
     }
 
     @Override
@@ -323,10 +321,6 @@
         PerformanceTracker.getInstanceForSensorId(getSensorId())
                 .incrementPermanentLockoutForUser(getTargetUserId());
 
-        try {
-            getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception", e);
-        }
+        onError(error, 0 /* vendorCode */);
     }
 }
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 1a53fec..c5037b7 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
@@ -465,7 +465,7 @@
                         BaseClientMonitor clientMonitor,
                         boolean success) {
                     mAuthSessionCoordinator.authEndedFor(userId, Utils.getCurrentStrength(sensorId),
-                            sensorId, requestId, success);
+                            sensorId, requestId, client.wasAuthSuccessful());
                 }
             });
         });
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 128ef0b..6c26e2b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -413,6 +413,11 @@
                                 Slog.e(TAG, "Remote exception in onAuthenticationAcquired()", e);
                             }
                         }
+
+                        @Override
+                        public void onAuthenticationHelp(int acquireInfo, CharSequence helpString) {
+                            onAuthenticationAcquired(acquireInfo);
+                        }
                     };
 
             return biometricPrompt.authenticateForOperation(
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 7e48f68..0f17139 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -969,15 +969,21 @@
         // Allow VpnManager app to temporarily run background services to handle this error.
         // If an app requires anything beyond this grace period, they MUST either declare
         // themselves as a foreground service, or schedule a job/workitem.
-        DeviceIdleInternal idleController = mDeps.getDeviceIdleInternal();
-        idleController.addPowerSaveTempWhitelistApp(Process.myUid(), packageName,
-                VPN_MANAGER_EVENT_ALLOWLIST_DURATION_MS, mUserId, false, REASON_VPN,
-                "VpnManager event");
+        final long token = Binder.clearCallingIdentity();
         try {
-            return mUserIdContext.startService(intent) != null;
-        } catch (RuntimeException e) {
-            Log.e(TAG, "Service of VpnManager app " + intent + " failed to start", e);
-            return false;
+            final DeviceIdleInternal idleController = mDeps.getDeviceIdleInternal();
+            idleController.addPowerSaveTempWhitelistApp(Process.myUid(), packageName,
+                    VPN_MANAGER_EVENT_ALLOWLIST_DURATION_MS, mUserId, false, REASON_VPN,
+                    "VpnManager event");
+
+            try {
+                return mUserIdContext.startService(intent) != null;
+            } catch (RuntimeException e) {
+                Log.e(TAG, "Service of VpnManager app " + intent + " failed to start", e);
+                return false;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
         }
     }
 
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 7802b9d..0e26d46 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -124,7 +124,7 @@
     private final boolean mDreamsEnabledByDefaultConfig;
     private final boolean mDreamsActivatedOnChargeByDefault;
     private final boolean mDreamsActivatedOnDockByDefault;
-    private final boolean mKeepDreamingWhenUndockedDefault;
+    private final boolean mKeepDreamingWhenUnpluggingDefault;
 
     private final CopyOnWriteArrayList<DreamManagerInternal.DreamManagerStateListener>
             mDreamManagerStateListeners = new CopyOnWriteArrayList<>();
@@ -236,8 +236,8 @@
         mDreamsActivatedOnDockByDefault = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault);
         mSettingsObserver = new SettingsObserver(mHandler);
-        mKeepDreamingWhenUndockedDefault = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_keepDreamingWhenUndocking);
+        mKeepDreamingWhenUnpluggingDefault = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_keepDreamingWhenUnplugging);
     }
 
     @Override
@@ -311,7 +311,7 @@
             pw.println("mIsDocked=" + mIsDocked);
             pw.println("mIsCharging=" + mIsCharging);
             pw.println("mWhenToDream=" + mWhenToDream);
-            pw.println("mKeepDreamingWhenUndockedDefault=" + mKeepDreamingWhenUndockedDefault);
+            pw.println("mKeepDreamingWhenUnpluggingDefault=" + mKeepDreamingWhenUnpluggingDefault);
             pw.println("getDozeComponent()=" + getDozeComponent());
             pw.println();
 
@@ -340,11 +340,11 @@
         }
     }
 
-    private void reportKeepDreamingWhenUndockedChanged(boolean keepDreaming) {
+    private void reportKeepDreamingWhenUnpluggingChanged(boolean keepDreaming) {
         mHandler.post(() -> {
             for (DreamManagerInternal.DreamManagerStateListener listener
                     : mDreamManagerStateListeners) {
-                listener.onKeepDreamingWhenUndockedChanged(keepDreaming);
+                listener.onKeepDreamingWhenUnpluggingChanged(keepDreaming);
             }
         });
     }
@@ -600,8 +600,7 @@
             }
 
             mSystemDreamComponent = componentName;
-            reportKeepDreamingWhenUndockedChanged(shouldKeepDreamingWhenUndocked());
-
+            reportKeepDreamingWhenUnpluggingChanged(shouldKeepDreamingWhenUnplugging());
             // Switch dream if currently dreaming and not dozing.
             if (isDreamingInternal() && !isDozingInternal()) {
                 startDreamInternal(false /*doze*/, (mSystemDreamComponent == null ? "clear" : "set")
@@ -610,8 +609,8 @@
         }
     }
 
-    private boolean shouldKeepDreamingWhenUndocked() {
-        return mKeepDreamingWhenUndockedDefault && mSystemDreamComponent == null;
+    private boolean shouldKeepDreamingWhenUnplugging() {
+        return mKeepDreamingWhenUnpluggingDefault && mSystemDreamComponent == null;
     }
 
     private ComponentName getDefaultDreamComponentForUser(int userId) {
@@ -1057,7 +1056,7 @@
         public void registerDreamManagerStateListener(DreamManagerStateListener listener) {
             mDreamManagerStateListeners.add(listener);
             // Initialize the listener's state.
-            listener.onKeepDreamingWhenUndockedChanged(shouldKeepDreamingWhenUndocked());
+            listener.onKeepDreamingWhenUnpluggingChanged(shouldKeepDreamingWhenUnplugging());
         }
 
         @Override
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
index 2ac2833..c039a83 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
@@ -334,8 +334,8 @@
     }
 
     private int installCert(ShellCommand shell) throws SystemFontException {
-        if (!(Build.IS_USERDEBUG || Build.IS_ENG)) {
-            throw new SecurityException("Only userdebug/eng device can add debug certificate");
+        if (!Build.IS_DEBUGGABLE) {
+            throw new SecurityException("Only debuggable device can add debug certificate");
         }
         if (Binder.getCallingUid() != Process.ROOT_UID) {
             throw new SecurityException("Only root can add debug certificate");
diff --git a/services/core/java/com/android/server/inputmethod/OWNERS b/services/core/java/com/android/server/inputmethod/OWNERS
index 00cd700..6e5eb56 100644
--- a/services/core/java/com/android/server/inputmethod/OWNERS
+++ b/services/core/java/com/android/server/inputmethod/OWNERS
@@ -1,8 +1,8 @@
 set noparent
 
-ogunwale@google.com
+roosa@google.com
 yukawa@google.com
 tarandeep@google.com
-lumark@google.com
-roosa@google.com
-wilsonwu@google.com
+
+ogunwale@google.com #{LAST_RESORT_SUGGESTION}
+jjaggi@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index 43e346a..2d40661 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -323,7 +323,7 @@
      */
     void notifyInstallerOfAppWhoseLocaleChanged(String appPackageName, int userId,
             LocaleList locales) {
-        String installingPackageName = getInstallingPackageName(appPackageName);
+        String installingPackageName = getInstallingPackageName(appPackageName, userId);
         if (installingPackageName != null) {
             Intent intent = createBaseIntent(Intent.ACTION_APPLICATION_LOCALE_CHANGED,
                     appPackageName, locales);
@@ -464,7 +464,7 @@
      * Checks if the calling app is the installer of the app whose locale changed.
      */
     private boolean isCallerInstaller(String appPackageName, int userId) {
-        String installingPackageName = getInstallingPackageName(appPackageName);
+        String installingPackageName = getInstallingPackageName(appPackageName, userId);
         if (installingPackageName != null) {
             // Get the uid of installer-on-record to compare with the calling uid.
             int installerUid = getPackageUid(installingPackageName, userId);
@@ -513,10 +513,11 @@
     }
 
     @Nullable
-    String getInstallingPackageName(String packageName) {
+    String getInstallingPackageName(String packageName, int userId) {
         try {
-            return mContext.getPackageManager()
-                    .getInstallSourceInfo(packageName).getInstallingPackageName();
+            return mContext.createContextAsUser(UserHandle.of(userId), /* flags= */
+                    0).getPackageManager().getInstallSourceInfo(
+                    packageName).getInstallingPackageName();
         } catch (PackageManager.NameNotFoundException e) {
             Slog.w(TAG, "Package not found " + packageName);
         }
diff --git a/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java b/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java
index 215c653..373d355 100644
--- a/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java
+++ b/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java
@@ -152,9 +152,10 @@
     void onPackageUpdateFinished(String packageName, int uid) {
         try {
             if ((!mUpdatedApps.contains(packageName)) && isUpdatedSystemApp(packageName)) {
+                int userId = UserHandle.getUserId(uid);
                 // If a system app is updated, verify that it has an installer-on-record.
                 String installingPackageName = mLocaleManagerService.getInstallingPackageName(
-                        packageName);
+                        packageName, userId);
                 if (installingPackageName == null) {
                     // We want to broadcast the locales info to the installer.
                     // If this app does not have an installer then do nothing.
@@ -162,7 +163,6 @@
                 }
 
                 try {
-                    int userId = UserHandle.getUserId(uid);
                     // Fetch the app-specific locales.
                     // If non-empty then send the info to the installer.
                     LocaleList appLocales = mLocaleManagerService.getApplicationLocales(
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 653b718..5f78374 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -519,17 +519,24 @@
         BroadcastReceiver btReceiver = new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
-                if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())
-                        || BluetoothAdapter.ACTION_BLE_STATE_CHANGED.equals(
-                        intent.getAction())) {
+                if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) {
                     sendBtSettingUpdate(/* forceUpdate= */ false);
                 }
             }
         };
         IntentFilter filter = new IntentFilter();
         filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
-        filter.addAction(BluetoothAdapter.ACTION_BLE_STATE_CHANGED);
         mContext.registerReceiver(btReceiver, filter);
+
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE),
+                /* notifyForDescendants= */ false,
+                new ContentObserver(/* handler= */ null) {
+                    @Override
+                    public void onChange(boolean selfChange) {
+                        sendBtSettingUpdate(/* forceUpdate= */ false);
+                    }
+                }, UserHandle.USER_ALL);
     }
 
     /**
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index 273afcc..dff02bf 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -15,44 +15,53 @@
  */
 package com.android.server.notification;
 
+import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
+import static android.app.Notification.FLAG_AUTO_CANCEL;
+import static android.app.Notification.FLAG_GROUP_SUMMARY;
+import static android.app.Notification.FLAG_LOCAL_ONLY;
+import static android.app.Notification.FLAG_NO_CLEAR;
+import static android.app.Notification.FLAG_ONGOING_EVENT;
+
+import android.annotation.NonNull;
 import android.service.notification.StatusBarNotification;
 import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.LinkedHashSet;
 import java.util.List;
-import java.util.Map;
 
 /**
  * NotificationManagerService helper for auto-grouping notifications.
  */
 public class GroupHelper {
     private static final String TAG = "GroupHelper";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     protected static final String AUTOGROUP_KEY = "ranker_group";
 
+    // Flags that all autogroup summaries have
+    protected static final int BASE_FLAGS =
+            FLAG_AUTOGROUP_SUMMARY | FLAG_GROUP_SUMMARY | FLAG_LOCAL_ONLY;
+    // Flag that autogroup summaries inherits if all children have the flag
+    private static final int ALL_CHILDREN_FLAG = FLAG_AUTO_CANCEL;
+    // Flags that autogroup summaries inherits if any child has them
+    private static final int ANY_CHILDREN_FLAGS = FLAG_ONGOING_EVENT | FLAG_NO_CLEAR;
+
     private final Callback mCallback;
     private final int mAutoGroupAtCount;
 
-    // count the number of ongoing notifications per group
-    // userId|packageName -> (set of ongoing notifications that aren't in an app group)
-    final ArrayMap<String, ArraySet<String>>
-            mOngoingGroupCount = new ArrayMap<>();
-
-    // Map of user : <Map of package : notification keys>. Only contains notifications that are not
-    // grouped by the app (aka no group or sort key).
-    Map<Integer, Map<String, LinkedHashSet<String>>> mUngroupedNotifications = new HashMap<>();
+    // Only contains notifications that are not explicitly grouped by the app (aka no group or
+    // sort key).
+    // userId|packageName -> (keys of notifications that aren't in an explicit app group -> flags)
+    @GuardedBy("mUngroupedNotifications")
+    private final ArrayMap<String, ArrayMap<String, Integer>> mUngroupedNotifications
+            = new ArrayMap<>();
 
     public GroupHelper(int autoGroupAtCount, Callback callback) {
         mAutoGroupAtCount = autoGroupAtCount;
-        mCallback = callback;
+        mCallback =  callback;
     }
 
     private String generatePackageKey(int userId, String pkg) {
@@ -60,69 +69,30 @@
     }
 
     @VisibleForTesting
-    protected int getOngoingGroupCount(int userId, String pkg) {
-        String key = generatePackageKey(userId, pkg);
-        return mOngoingGroupCount.getOrDefault(key, new ArraySet<>(0)).size();
+    @GuardedBy("mUngroupedNotifications")
+    protected int getAutogroupSummaryFlags(@NonNull final ArrayMap<String, Integer> children) {
+        boolean allChildrenHasFlag = children.size() > 0;
+        int anyChildFlagSet = 0;
+        for (int i = 0; i < children.size(); i++) {
+            if (!hasAnyFlag(children.valueAt(i), ALL_CHILDREN_FLAG)) {
+                allChildrenHasFlag = false;
+            }
+            if (hasAnyFlag(children.valueAt(i), ANY_CHILDREN_FLAGS)) {
+                anyChildFlagSet |= (children.valueAt(i) & ANY_CHILDREN_FLAGS);
+            }
+        }
+        return BASE_FLAGS | (allChildrenHasFlag ? ALL_CHILDREN_FLAG : 0) | anyChildFlagSet;
     }
 
-    private void updateOngoingGroupCount(StatusBarNotification sbn, boolean add) {
-        if (sbn.getNotification().isGroupSummary()) {
-            return;
-        }
-        String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName());
-        ArraySet<String> notifications = mOngoingGroupCount.getOrDefault(key, new ArraySet<>(0));
-        if (add) {
-            notifications.add(sbn.getKey());
-            mOngoingGroupCount.put(key, notifications);
-        } else {
-            notifications.remove(sbn.getKey());
-            // we don't need to put it back if it is default
-        }
-
-        boolean needsOngoingFlag = notifications.size() > 0;
-        mCallback.updateAutogroupSummary(sbn.getUserId(), sbn.getPackageName(), needsOngoingFlag);
-    }
-
-    public void onNotificationUpdated(StatusBarNotification childSbn) {
-        updateOngoingGroupCount(childSbn, childSbn.isOngoing() && !childSbn.isAppGroup());
+    private boolean hasAnyFlag(int flags, int mask) {
+        return (flags & mask) != 0;
     }
 
     public void onNotificationPosted(StatusBarNotification sbn, boolean autogroupSummaryExists) {
         try {
-            updateOngoingGroupCount(sbn, sbn.isOngoing() && !sbn.isAppGroup());
-
-            List<String> notificationsToGroup = new ArrayList<>();
             if (!sbn.isAppGroup()) {
-                // Not grouped by the app, add to the list of notifications for the app;
-                // send grouping update if app exceeds the autogrouping limit.
-                synchronized (mUngroupedNotifications) {
-                    Map<String, LinkedHashSet<String>> ungroupedNotificationsByUser
-                            = mUngroupedNotifications.get(sbn.getUserId());
-                    if (ungroupedNotificationsByUser == null) {
-                        ungroupedNotificationsByUser = new HashMap<>();
-                    }
-                    mUngroupedNotifications.put(sbn.getUserId(), ungroupedNotificationsByUser);
-                    LinkedHashSet<String> notificationsForPackage
-                            = ungroupedNotificationsByUser.get(sbn.getPackageName());
-                    if (notificationsForPackage == null) {
-                        notificationsForPackage = new LinkedHashSet<>();
-                    }
-
-                    notificationsForPackage.add(sbn.getKey());
-                    ungroupedNotificationsByUser.put(sbn.getPackageName(), notificationsForPackage);
-
-                    if (notificationsForPackage.size() >= mAutoGroupAtCount
-                            || autogroupSummaryExists) {
-                        notificationsToGroup.addAll(notificationsForPackage);
-                    }
-                }
-                if (notificationsToGroup.size() > 0) {
-                    adjustAutogroupingSummary(sbn.getUserId(), sbn.getPackageName(),
-                            notificationsToGroup.get(0), true);
-                    adjustNotificationBundling(notificationsToGroup, true);
-                }
+                maybeGroup(sbn, autogroupSummaryExists);
             } else {
-                // Grouped, but not by us. Send updates to un-autogroup, if we grouped it.
                 maybeUngroup(sbn, false, sbn.getUserId());
             }
 
@@ -133,7 +103,6 @@
 
     public void onNotificationRemoved(StatusBarNotification sbn) {
         try {
-            updateOngoingGroupCount(sbn, false);
             maybeUngroup(sbn, true, sbn.getUserId());
         } catch (Exception e) {
             Slog.e(TAG, "Error processing canceled notification", e);
@@ -141,70 +110,114 @@
     }
 
     /**
-     * Un-autogroups notifications that are now grouped by the app.
+     * A non-app grouped notification has been added or updated
+     * Evaluate if:
+     * (a) an existing autogroup summary needs updated flags
+     * (b) a new autogroup summary needs to be added with correct flags
+     * (c) other non-app grouped children need to be moved to the autogroup
+     *
+     * And stores the list of upgrouped notifications & their flags
+     */
+    private void maybeGroup(StatusBarNotification sbn, boolean autogroupSummaryExists) {
+        int flags = 0;
+        List<String> notificationsToGroup = new ArrayList<>();
+        synchronized (mUngroupedNotifications) {
+            String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName());
+            final ArrayMap<String, Integer> children =
+                    mUngroupedNotifications.getOrDefault(key, new ArrayMap<>());
+
+            children.put(sbn.getKey(), sbn.getNotification().flags);
+            mUngroupedNotifications.put(key, children);
+
+            if (children.size() >= mAutoGroupAtCount || autogroupSummaryExists) {
+                flags = getAutogroupSummaryFlags(children);
+                notificationsToGroup.addAll(children.keySet());
+            }
+        }
+        if (notificationsToGroup.size() > 0) {
+            if (autogroupSummaryExists) {
+                mCallback.updateAutogroupSummary(sbn.getUserId(), sbn.getPackageName(), flags);
+            } else {
+                mCallback.addAutoGroupSummary(
+                        sbn.getUserId(), sbn.getPackageName(), sbn.getKey(), flags);
+            }
+            for (String key : notificationsToGroup) {
+                mCallback.addAutoGroup(key);
+            }
+        }
+    }
+
+    /**
+     * A notification was added that's app grouped, or a notification was removed.
+     * Evaluate whether:
+     * (a) an existing autogroup summary needs updated flags
+     * (b) if we need to remove our autogroup overlay for this notification
+     * (c) we need to remove the autogroup summary
+     *
+     * And updates the internal state of un-app-grouped notifications and their flags
      */
     private void maybeUngroup(StatusBarNotification sbn, boolean notificationGone, int userId) {
-        List<String> notificationsToUnAutogroup = new ArrayList<>();
         boolean removeSummary = false;
+        int summaryFlags = 0;
+        boolean updateSummaryFlags = false;
+        boolean removeAutogroupOverlay = false;
         synchronized (mUngroupedNotifications) {
-            Map<String, LinkedHashSet<String>> ungroupedNotificationsByUser
-                    = mUngroupedNotifications.get(sbn.getUserId());
-            if (ungroupedNotificationsByUser == null || ungroupedNotificationsByUser.size() == 0) {
+            String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName());
+            final ArrayMap<String, Integer> children =
+                    mUngroupedNotifications.getOrDefault(key, new ArrayMap<>());
+            if (children.size() == 0) {
                 return;
             }
-            LinkedHashSet<String> notificationsForPackage
-                    = ungroupedNotificationsByUser.get(sbn.getPackageName());
-            if (notificationsForPackage == null || notificationsForPackage.size() == 0) {
-                return;
-            }
-            if (notificationsForPackage.remove(sbn.getKey())) {
-                if (!notificationGone) {
-                    // Add the current notification to the ungrouping list if it still exists.
-                    notificationsToUnAutogroup.add(sbn.getKey());
+
+            // if this notif was autogrouped and now isn't
+            if (children.containsKey(sbn.getKey())) {
+                // if this notification was contributing flags that aren't covered by other
+                // children to the summary, reevaluate flags for the summary
+                int flags = children.remove(sbn.getKey());
+                // this
+                if (hasAnyFlag(flags, ANY_CHILDREN_FLAGS)) {
+                    updateSummaryFlags = true;
+                    summaryFlags = getAutogroupSummaryFlags(children);
                 }
-            }
-            // If the status change of this notification has brought the number of loose
-            // notifications to zero, remove the summary and un-autogroup.
-            if (notificationsForPackage.size() == 0) {
-                ungroupedNotificationsByUser.remove(sbn.getPackageName());
-                removeSummary = true;
+                // if this notification still exists and has an autogroup overlay, but is now
+                // grouped by the app, clear the overlay
+                if (!notificationGone && sbn.getOverrideGroupKey() != null) {
+                    removeAutogroupOverlay = true;
+                }
+
+                // If there are no more children left to autogroup, remove the summary
+                if (children.size() == 0) {
+                    removeSummary = true;
+                }
             }
         }
         if (removeSummary) {
-            adjustAutogroupingSummary(userId, sbn.getPackageName(), null, false);
-        }
-        if (notificationsToUnAutogroup.size() > 0) {
-            adjustNotificationBundling(notificationsToUnAutogroup, false);
-        }
-    }
-
-    private void adjustAutogroupingSummary(int userId, String packageName, String triggeringKey,
-            boolean summaryNeeded) {
-        if (summaryNeeded) {
-            mCallback.addAutoGroupSummary(userId, packageName, triggeringKey,
-                    getOngoingGroupCount(userId, packageName) > 0);
+            mCallback.removeAutoGroupSummary(userId, sbn.getPackageName());
         } else {
-            mCallback.removeAutoGroupSummary(userId, packageName);
+            if (updateSummaryFlags) {
+                mCallback.updateAutogroupSummary(userId, sbn.getPackageName(), summaryFlags);
+            }
+        }
+        if (removeAutogroupOverlay) {
+            mCallback.removeAutoGroup(sbn.getKey());
         }
     }
 
-    private void adjustNotificationBundling(List<String> keys, boolean group) {
-        for (String key : keys) {
-            if (DEBUG) Log.i(TAG, "Sending grouping adjustment for: " + key + " group? " + group);
-            if (group) {
-                mCallback.addAutoGroup(key);
-            } else {
-                mCallback.removeAutoGroup(key);
-            }
+    @VisibleForTesting
+    int getNotGroupedByAppCount(int userId, String pkg) {
+        synchronized (mUngroupedNotifications) {
+            String key = generatePackageKey(userId, pkg);
+            final ArrayMap<String, Integer> children =
+                    mUngroupedNotifications.getOrDefault(key, new ArrayMap<>());
+            return children.size();
         }
     }
 
     protected interface Callback {
         void addAutoGroup(String key);
         void removeAutoGroup(String key);
-        void addAutoGroupSummary(int userId, String pkg, String triggeringKey,
-                boolean needsOngoingFlag);
+        void addAutoGroupSummary(int userId, String pkg, String triggeringKey, int flags);
         void removeAutoGroupSummary(int user, String pkg);
-        void updateAutogroupSummary(int userId, String pkg, boolean needsOngoingFlag);
+        void updateAutogroupSummary(int userId, String pkg, int flags);
     }
 }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 5d81dda..ebcbfed 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -319,6 +319,7 @@
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.policy.PermissionPolicyInternal;
+import com.android.server.powerstats.StatsPullAtomCallbackImpl;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.uri.UriGrantsManagerInternal;
 import com.android.server.utils.Slogf;
@@ -902,11 +903,11 @@
      * has the same flag. It will delete the flag otherwise
      * @param userId user id of the autogroup summary
      * @param pkg package of the autogroup summary
-     * @param needsOngoingFlag true if the group has at least one ongoing notification
+     * @param flags the new flags for this summary
      * @param isAppForeground true if the app is currently in the foreground.
      */
     @GuardedBy("mNotificationLock")
-    protected void updateAutobundledSummaryFlags(int userId, String pkg, boolean needsOngoingFlag,
+    protected void updateAutobundledSummaryFlags(int userId, String pkg, int flags,
             boolean isAppForeground) {
         ArrayMap<String, String> summaries = mAutobundledSummaries.get(userId);
         if (summaries == null) {
@@ -921,13 +922,8 @@
             return;
         }
         int oldFlags = summary.getSbn().getNotification().flags;
-        if (needsOngoingFlag) {
-            summary.getSbn().getNotification().flags |= FLAG_ONGOING_EVENT;
-        } else {
-            summary.getSbn().getNotification().flags &= ~FLAG_ONGOING_EVENT;
-        }
-
-        if (summary.getSbn().getNotification().flags != oldFlags) {
+        if (oldFlags != flags) {
+            summary.getSbn().getNotification().flags = flags;
             mHandler.post(new EnqueueNotificationRunnable(userId, summary, isAppForeground,
                     SystemClock.elapsedRealtime()));
         }
@@ -2684,9 +2680,14 @@
 
             @Override
             public void addAutoGroupSummary(int userId, String pkg, String triggeringKey,
-                    boolean needsOngoingFlag) {
-                NotificationManagerService.this.addAutoGroupSummary(
-                        userId, pkg, triggeringKey, needsOngoingFlag);
+                    int flags) {
+                NotificationRecord r = createAutoGroupSummary(userId, pkg, triggeringKey, flags);
+                if (r != null) {
+                    final boolean isAppForeground =
+                            mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
+                    mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground,
+                            SystemClock.elapsedRealtime()));
+                }
             }
 
             @Override
@@ -2697,11 +2698,11 @@
             }
 
             @Override
-            public void updateAutogroupSummary(int userId, String pkg, boolean needsOngoingFlag) {
+            public void updateAutogroupSummary(int userId, String pkg, int flags) {
                 boolean isAppForeground = pkg != null
                         && mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
                 synchronized (mNotificationLock) {
-                    updateAutobundledSummaryFlags(userId, pkg, needsOngoingFlag, isAppForeground);
+                    updateAutobundledSummaryFlags(userId, pkg, flags, isAppForeground);
                 }
             }
         });
@@ -3334,7 +3335,7 @@
             }
 
             checkCallerIsSameApp(pkg);
-            final boolean isSystemToast = isCallerSystemOrPhone()
+            final boolean isSystemToast = isCallerIsSystemOrSystemUi()
                     || PackageManagerService.PLATFORM_PACKAGE_NAME.equals(pkg);
             boolean isAppRenderedToast = (callback != null);
             if (!checkCanEnqueueToast(pkg, callingUid, displayId, isAppRenderedToast,
@@ -5961,19 +5962,6 @@
         r.addAdjustment(adjustment);
     }
 
-    @VisibleForTesting
-    void addAutoGroupSummary(int userId, String pkg, String triggeringKey,
-            boolean needsOngoingFlag) {
-        NotificationRecord r = createAutoGroupSummary(
-                userId, pkg, triggeringKey, needsOngoingFlag);
-        if (r != null) {
-            final boolean isAppForeground =
-                    mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
-            mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground,
-                    SystemClock.elapsedRealtime()));
-        }
-    }
-
     // Clears the 'fake' auto-group summary.
     @VisibleForTesting
     @GuardedBy("mNotificationLock")
@@ -5997,7 +5985,7 @@
 
     // Creates a 'fake' summary for a package that has exceeded the solo-notification limit.
     NotificationRecord createAutoGroupSummary(int userId, String pkg, String triggeringKey,
-            boolean needsOngoingFlag) {
+            int flagsToSet) {
         NotificationRecord summaryRecord = null;
         boolean isPermissionFixed = mPermissionHelper.isPermissionFixed(pkg, userId);
         synchronized (mNotificationLock) {
@@ -6007,7 +5995,6 @@
                 // adjustment will post a summary if needed.
                 return null;
             }
-            NotificationChannel channel = notificationRecord.getChannel();
             final StatusBarNotification adjustedSbn = notificationRecord.getSbn();
             userId = adjustedSbn.getUser().getIdentifier();
             int uid =  adjustedSbn.getUid();
@@ -6030,11 +6017,8 @@
                                 .setGroupSummary(true)
                                 .setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN)
                                 .setGroup(GroupHelper.AUTOGROUP_KEY)
-                                .setFlag(FLAG_AUTOGROUP_SUMMARY, true)
-                                .setFlag(Notification.FLAG_GROUP_SUMMARY, true)
-                                .setFlag(FLAG_ONGOING_EVENT, needsOngoingFlag)
+                                .setFlag(flagsToSet, true)
                                 .setColor(adjustedSbn.getNotification().color)
-                                .setLocalOnly(true)
                                 .build();
                 summaryNotification.extras.putAll(extras);
                 Intent appIntent = getContext().getPackageManager().getLaunchIntentForPackage(pkg);
@@ -6372,6 +6356,7 @@
      * The private API only accessible to the system process.
      */
     private final NotificationManagerInternal mInternalService = new NotificationManagerInternal() {
+
         @Override
         public NotificationChannel getNotificationChannel(String pkg, int uid, String
                 channelId) {
@@ -7827,18 +7812,17 @@
                     if (notification.getSmallIcon() != null) {
                         StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;
                         mListeners.notifyPostedLocked(r, old);
-                        if ((oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup()))
-                                && !isCritical(r)) {
-                            mHandler.post(() -> {
-                                synchronized (mNotificationLock) {
-                                    mGroupHelper.onNotificationPosted(
-                                            n, hasAutoGroupSummaryLocked(n));
-                                }
-                            });
-                        } else if (oldSbn != null) {
-                            final NotificationRecord finalRecord = r;
-                            mHandler.post(() ->
-                                    mGroupHelper.onNotificationUpdated(finalRecord.getSbn()));
+                        if (oldSbn == null
+                                || !Objects.equals(oldSbn.getGroup(), n.getGroup())
+                                || oldSbn.getNotification().flags != n.getNotification().flags) {
+                            if (!isCritical(r)) {
+                                mHandler.post(() -> {
+                                    synchronized (mNotificationLock) {
+                                        mGroupHelper.onNotificationPosted(
+                                                n, hasAutoGroupSummaryLocked(n));
+                                    }
+                                });
+                            }
                         }
                     } else {
                         Slog.e(TAG, "Not posting notification without small icon: " + notification);
diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java
index 35b94e7..88d23ce 100644
--- a/services/core/java/com/android/server/notification/ZenLog.java
+++ b/services/core/java/com/android/server/notification/ZenLog.java
@@ -27,6 +27,7 @@
 import android.service.notification.IConditionProvider;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeDiff;
 import android.util.Log;
 import android.util.Slog;
 
@@ -146,13 +147,13 @@
 
     public static void traceConfig(String reason, ZenModeConfig oldConfig,
             ZenModeConfig newConfig) {
-        ZenModeConfig.Diff diff = ZenModeConfig.diff(oldConfig, newConfig);
-        if (diff.isEmpty()) {
+        ZenModeDiff.ConfigDiff diff = new ZenModeDiff.ConfigDiff(oldConfig, newConfig);
+        if (diff == null || !diff.hasDiff()) {
             append(TYPE_CONFIG, reason + " no changes");
         } else {
             append(TYPE_CONFIG, reason
                     + ",\n" + (newConfig != null ? newConfig.toString() : null)
-                    + ",\n" + ZenModeConfig.diff(oldConfig, newConfig));
+                    + ",\n" + diff);
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 596e9b9..69ef3f7 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -994,7 +994,7 @@
                     reconciledPackages = ReconcilePackageUtils.reconcilePackages(
                             requests, Collections.unmodifiableMap(mPm.mPackages),
                             versionInfos, mSharedLibraries, mPm.mSettings.getKeySetManagerService(),
-                            mPm.mSettings);
+                            mPm.mSettings, mContext);
                 } catch (ReconcileFailure e) {
                     for (InstallRequest request : requests) {
                         request.setError("Reconciliation failed...", e);
@@ -3930,7 +3930,7 @@
                                 mPm.mPackages, Collections.singletonMap(pkgName,
                                         mPm.getSettingsVersionForPackage(parsedPackage)),
                                 mSharedLibraries, mPm.mSettings.getKeySetManagerService(),
-                                mPm.mSettings);
+                                mPm.mSettings, mContext);
                 if ((scanFlags & SCAN_AS_APEX) == 0) {
                     appIdCreated = optimisticallyRegisterAppId(installRequest);
                 } else {
diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
index 5312ae6..e3c97e9 100644
--- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
+++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
@@ -16,6 +16,7 @@
 
 package com.android.server.pm;
 
+import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
 import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
 import static android.content.pm.SigningDetails.CapabilityMergeRule.MERGE_RESTRICTED_CAPABILITY;
@@ -23,19 +24,24 @@
 import static com.android.server.pm.PackageManagerService.SCAN_BOOTING;
 import static com.android.server.pm.PackageManagerService.SCAN_DONT_KILL_APP;
 
+import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.SigningDetails;
 import android.os.SystemProperties;
+import android.permission.PermissionManager;
 import android.util.ArrayMap;
 import android.util.Log;
 
 import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.component.ParsedUsesPermission;
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.utils.WatchedLongSparseArray;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 
@@ -54,7 +60,7 @@
             Map<String, AndroidPackage> allPackages,
             Map<String, Settings.VersionInfo> versionInfos,
             SharedLibrariesImpl sharedLibraries,
-            KeySetManagerService ksms, Settings settings)
+            KeySetManagerService ksms, Settings settings, Context context)
             throws ReconcileFailure {
         final List<ReconciledPackage> result = new ArrayList<>(installRequests.size());
 
@@ -143,11 +149,11 @@
                 } else {
                     if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
                         throw new ReconcileFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
-                                "Package " + parsedPackage.getPackageName()
+                                "Package " + installPackageName
                                         + " upgrade keys do not match the previously installed"
                                         + " version");
                     } else {
-                        String msg = "System package " + parsedPackage.getPackageName()
+                        String msg = "System package " + installPackageName
                                 + " signature changed; retaining data.";
                         PackageManagerService.reportSettingsProblem(Log.WARN, msg);
                     }
@@ -168,11 +174,42 @@
                         removeAppKeySetData = true;
                     }
 
-                    // if this is is a sharedUser, check to see if the new package is signed by a
-                    // newer
-                    // signing certificate than the existing one, and if so, copy over the new
+                    // if this is a sharedUser, check to see if the new package is signed by a
+                    // newer signing certificate than the existing one, and if so, copy over the new
                     // details
                     if (sharedUserSetting != null) {
+                        if (!parsedPackage.isTestOnly() && sharedUserSetting.isPrivileged()
+                                && !signatureCheckPs.isSystem()) {
+                            final List<ParsedUsesPermission> usesPermissions =
+                                    parsedPackage.getUsesPermissions();
+                            final List<String> usesPrivilegedPermissions = new ArrayList<>();
+                            final PermissionManager permissionManager = context.getSystemService(
+                                    PermissionManager.class);
+                            // Check if the app requests any privileged permissions because that
+                            // violates the privapp-permissions allowlist check during boot.
+                            if (permissionManager != null) {
+                                for (int i = 0; i < usesPermissions.size(); i++) {
+                                    final String permissionName = usesPermissions.get(i).getName();
+                                    final PermissionInfo permissionInfo =
+                                            permissionManager.getPermissionInfo(permissionName, 0);
+                                    if (permissionInfo != null
+                                            && (permissionInfo.getProtectionFlags()
+                                            & PermissionInfo.PROTECTION_FLAG_PRIVILEGED) != 0) {
+                                        usesPrivilegedPermissions.add(permissionName);
+                                    }
+                                }
+                            }
+
+                            if (!usesPrivilegedPermissions.isEmpty()) {
+                                throw new ReconcileFailure(INSTALL_FAILED_INVALID_APK,
+                                        "Non-system package: " + installPackageName
+                                                + " shares signature and sharedUserId with"
+                                                + " a privileged package but requests"
+                                                + " privileged permissions that are not"
+                                                + " allowed: " + Arrays.toString(
+                                                        usesPrivilegedPermissions.toArray()));
+                            }
+                        }
                         // Attempt to merge the existing lineage for the shared SigningDetails with
                         // the lineage of the new package; if the shared SigningDetails are not
                         // returned this indicates the new package added new signers to the lineage
@@ -189,7 +226,7 @@
                             for (AndroidPackage androidPackage : sharedUserSetting.getPackages()) {
                                 if (androidPackage.getPackageName() != null
                                         && !androidPackage.getPackageName().equals(
-                                        parsedPackage.getPackageName())) {
+                                                installPackageName)) {
                                     mergedDetails = mergedDetails.mergeLineageWith(
                                             androidPackage.getSigningDetails(),
                                             MERGE_RESTRICTED_CAPABILITY);
@@ -219,7 +256,7 @@
                     if (sharedUserSetting != null) {
                         if (sharedUserSetting.signaturesChanged != null
                                 && !PackageManagerServiceUtils.canJoinSharedUserId(
-                                parsedPackage.getPackageName(), parsedPackage.getSigningDetails(),
+                                installPackageName, parsedPackage.getSigningDetails(),
                                 sharedUserSetting,
                                 PackageManagerServiceUtils.SHARED_USER_ID_JOIN_TYPE_SYSTEM)) {
                             if (SystemProperties.getInt("ro.product.first_api_level", 0) <= 29) {
@@ -240,7 +277,7 @@
                                 // whichever package happened to be scanned later.
                                 throw new IllegalStateException(
                                         "Signature mismatch on system package "
-                                                + parsedPackage.getPackageName()
+                                                + installPackageName
                                                 + " for shared user "
                                                 + sharedUserSetting);
                             }
@@ -252,7 +289,7 @@
                         sharedUserSetting.signaturesChanged = Boolean.TRUE;
                     }
                     // File a report about this.
-                    String msg = "System package " + parsedPackage.getPackageName()
+                    String msg = "System package " + installPackageName
                             + " signature changed; retaining data.";
                     PackageManagerService.reportSettingsProblem(Log.WARN, msg);
                 } catch (IllegalArgumentException e) {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 73be549..5f8efe2 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -2641,6 +2641,7 @@
 
     private void setUserRestrictionInner(int userId, @NonNull String key, boolean value) {
         if (!UserRestrictionsUtils.isValidRestriction(key)) {
+            Slog.e(LOG_TAG, "Setting invalid restriction " + key);
             return;
         }
         synchronized (mRestrictionsLock) {
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index e5e32f0..4d2b119 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -206,8 +206,6 @@
     static {
         SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS);
         SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND);
-        SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE);
-        SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND);
     }
 
     private static final Set<String> STORAGE_PERMISSIONS = new ArraySet<>();
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index c5f939a..297ad73 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1332,9 +1332,7 @@
                     // Bg location is one-off runtime modifier permission and has no app op
                     if (sPlatformPermissions.containsKey(permission)
                             && !Manifest.permission.ACCESS_BACKGROUND_LOCATION.equals(permission)
-                            && !Manifest.permission.BODY_SENSORS_BACKGROUND.equals(permission)
-                            && !Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND
-                            .equals(permission)) {
+                            && !Manifest.permission.BODY_SENSORS_BACKGROUND.equals(permission)) {
                         Slog.wtf(LOG_TAG, "Platform runtime permission " + permission
                                 + " with no app op defined!");
                     }
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index cc2c9ad..3492b26 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -23,6 +23,7 @@
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_ERRORED;
 import static android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_DEFAULT;
+import static android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_DENIED;
 import static android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED;
 import static android.content.pm.PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
@@ -3655,6 +3656,26 @@
 
         for (String permission : pkg.getRequestedPermissions()) {
             Integer permissionState = permissionStates.get(permission);
+
+            if (Objects.equals(permission, Manifest.permission.USE_FULL_SCREEN_INTENT)
+                    && permissionState == null) {
+                final PackageStateInternal ps;
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    ps = mPackageManagerInt.getPackageStateInternal(pkg.getPackageName());
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+                final String[] useFullScreenIntentPackageNames =
+                        mContext.getResources().getStringArray(
+                                com.android.internal.R.array.config_useFullScreenIntentPackages);
+                final boolean canUseFullScreenIntent = (ps != null && ps.isSystem())
+                        || ArrayUtils.contains(useFullScreenIntentPackageNames,
+                                pkg.getPackageName());
+                permissionState = canUseFullScreenIntent ? PERMISSION_STATE_GRANTED
+                        : PERMISSION_STATE_DENIED;
+            }
+
             if (permissionState == null || permissionState == PERMISSION_STATE_DEFAULT) {
                 continue;
             }
diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java
index 401eac6..7a5664f 100644
--- a/services/core/java/com/android/server/policy/AppOpsPolicy.java
+++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java
@@ -316,6 +316,7 @@
     private int resolveDatasourceOp(int code, int uid, @NonNull String packageName,
             @Nullable String attributionTag) {
         code = resolveRecordAudioOp(code, uid);
+        code = resolveSandboxedServiceOp(code, uid);
         if (attributionTag == null) {
             return code;
         }
@@ -439,6 +440,28 @@
         return code;
     }
 
+    private int resolveSandboxedServiceOp(int code, int uid) {
+        if (!Process.isIsolated(uid) // simple check which fails-fast for the common case
+                 || !(code == AppOpsManager.OP_RECORD_AUDIO || code == AppOpsManager.OP_CAMERA)) {
+            return code;
+        }
+        final HotwordDetectionServiceIdentity hotwordDetectionServiceIdentity =
+                mVoiceInteractionManagerInternal.getHotwordDetectionServiceIdentity();
+        if (hotwordDetectionServiceIdentity != null
+                && uid == hotwordDetectionServiceIdentity.getIsolatedUid()) {
+            // Upgrade the op such that no indicators is shown for camera or audio service. This
+            // will bypass the permission checking for the original OP_RECORD_AUDIO and OP_CAMERA.
+            switch (code) {
+                case AppOpsManager.OP_RECORD_AUDIO:
+                    return AppOpsManager.OP_RECORD_AUDIO_SANDBOXED;
+                case AppOpsManager.OP_CAMERA:
+                    return AppOpsManager.OP_CAMERA_SANDBOXED;
+            }
+        }
+        return code;
+    }
+
+
     private int resolveUid(int code, int uid) {
         // The HotwordDetectionService is an isolated service, which ordinarily cannot hold
         // permissions. So we allow it to assume the owning package identity for certain
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 8165958..fc6b4e9 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -222,6 +222,7 @@
 import com.android.server.policy.keyguard.KeyguardStateMonitor.StateCallback;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.vr.VrManagerInternal;
+import com.android.server.wallpaper.WallpaperManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.DisplayPolicy;
 import com.android.server.wm.DisplayRotation;
@@ -412,6 +413,9 @@
     SensorPrivacyManager mSensorPrivacyManager;
     DisplayManager mDisplayManager;
     DisplayManagerInternal mDisplayManagerInternal;
+
+    private WallpaperManagerInternal mWallpaperManagerInternal;
+
     boolean mPreloadedRecentApps;
     final Object mServiceAcquireLock = new Object();
     Vibrator mVibrator; // Vibrator for giving feedback of orientation changes
@@ -5016,11 +5020,34 @@
         return bootCompleted ? mKeyguardDrawnTimeout : 5000;
     }
 
+    @Nullable
+    private WallpaperManagerInternal getWallpaperManagerInternal() {
+        if (mWallpaperManagerInternal == null) {
+            mWallpaperManagerInternal = LocalServices.getService(WallpaperManagerInternal.class);
+        }
+        return mWallpaperManagerInternal;
+    }
+
+    private void reportScreenTurningOnToWallpaper(int displayId) {
+        WallpaperManagerInternal wallpaperManagerInternal = getWallpaperManagerInternal();
+        if (wallpaperManagerInternal != null) {
+            wallpaperManagerInternal.onScreenTurningOn(displayId);
+        }
+    }
+
+    private void reportScreenTurnedOnToWallpaper(int displayId) {
+        WallpaperManagerInternal wallpaperManagerInternal = getWallpaperManagerInternal();
+        if (wallpaperManagerInternal != null) {
+            wallpaperManagerInternal.onScreenTurnedOn(displayId);
+        }
+    }
+
     // Called on the DisplayManager's DisplayPowerController thread.
     @Override
     public void screenTurningOn(int displayId, final ScreenOnListener screenOnListener) {
         if (DEBUG_WAKEUP) Slog.i(TAG, "Display " + displayId + " turning on...");
 
+        reportScreenTurningOnToWallpaper(displayId);
         if (displayId == DEFAULT_DISPLAY) {
             Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "screenTurningOn",
                     0 /* cookie */);
@@ -5061,6 +5088,8 @@
     public void screenTurnedOn(int displayId) {
         if (DEBUG_WAKEUP) Slog.i(TAG, "Display " + displayId + " turned on...");
 
+        reportScreenTurnedOnToWallpaper(displayId);
+
         if (displayId != DEFAULT_DISPLAY) {
             return;
         }
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 2e8a150..e392c24 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -667,15 +667,15 @@
     // but the DreamService has not yet been told to start (it's an async process).
     private boolean mDozeStartInProgress;
 
-    // Whether to keep dreaming when the device is undocked.
-    private boolean mKeepDreamingWhenUndocked;
+    // Whether to keep dreaming when the device is unplugging.
+    private boolean mKeepDreamingWhenUnplugging;
 
     private final class DreamManagerStateListener implements
             DreamManagerInternal.DreamManagerStateListener {
         @Override
-        public void onKeepDreamingWhenUndockedChanged(boolean keepDreaming) {
+        public void onKeepDreamingWhenUnpluggingChanged(boolean keepDreaming) {
             synchronized (mLock) {
-                mKeepDreamingWhenUndocked = keepDreaming;
+                mKeepDreamingWhenUnplugging = keepDreaming;
             }
         }
     }
@@ -2504,14 +2504,12 @@
             return false;
         }
 
-        // Don't wake when undocking while dreaming if configured not to.
-        if (mKeepDreamingWhenUndocked
+        // Don't wake when unplugging while dreaming if configured not to.
+        if (mKeepDreamingWhenUnplugging
                 && getGlobalWakefulnessLocked() == WAKEFULNESS_DREAMING
-                && wasPowered && !mIsPowered
-                && oldPlugType == BatteryManager.BATTERY_PLUGGED_DOCK) {
+                && wasPowered && !mIsPowered) {
             return false;
         }
-
         // Don't wake when undocked from wireless charger.
         // See WirelessChargerDetector for justification.
         if (wasPowered && !mIsPowered
@@ -4477,7 +4475,7 @@
                     + mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig);
             pw.println("  mTheaterModeEnabled="
                     + mTheaterModeEnabled);
-            pw.println("  mKeepDreamingWhenUndocked=" + mKeepDreamingWhenUndocked);
+            pw.println("  mKeepDreamingWhenUnplugging=" + mKeepDreamingWhenUnplugging);
             pw.println("  mSuspendWhenScreenOffDueToProximityConfig="
                     + mSuspendWhenScreenOffDueToProximityConfig);
             pw.println("  mDreamsSupportedConfig=" + mDreamsSupportedConfig);
diff --git a/services/core/java/com/android/server/power/ShutdownCheckPoints.java b/services/core/java/com/android/server/power/ShutdownCheckPoints.java
index 32f1bcf..546dc81 100644
--- a/services/core/java/com/android/server/power/ShutdownCheckPoints.java
+++ b/services/core/java/com/android/server/power/ShutdownCheckPoints.java
@@ -295,11 +295,18 @@
         @Nullable
         String getProcessName() {
             try {
-                List<ActivityManager.RunningAppProcessInfo> runningProcesses =
-                        mActivityManager.getRunningAppProcesses();
-                for (ActivityManager.RunningAppProcessInfo processInfo : runningProcesses) {
-                    if (processInfo.pid == mCallerProcessId) {
-                        return processInfo.processName;
+                List<ActivityManager.RunningAppProcessInfo> runningProcesses = null;
+                if (mActivityManager != null) {
+                    runningProcesses = mActivityManager.getRunningAppProcesses();
+                } else {
+                    Slog.v(TAG, "No ActivityManager available to find process name with pid="
+                            + mCallerProcessId);
+                }
+                if (runningProcesses != null) {
+                    for (ActivityManager.RunningAppProcessInfo processInfo : runningProcesses) {
+                        if (processInfo.pid == mCallerProcessId) {
+                            return processInfo.processName;
+                        }
                     }
                 }
             } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
index c2d4ac6..b1430e7 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -703,17 +703,20 @@
             // vibrate before shutting down
             Vibrator vibrator = new SystemVibrator(context);
             try {
-                vibrator.vibrate(SHUTDOWN_VIBRATE_MS, VIBRATION_ATTRIBUTES);
+                if (vibrator.hasVibrator()) {
+                    vibrator.vibrate(SHUTDOWN_VIBRATE_MS, VIBRATION_ATTRIBUTES);
+                    // vibrator is asynchronous so we need to wait to avoid shutting down too soon.
+                    try {
+                        Thread.sleep(SHUTDOWN_VIBRATE_MS);
+                    } catch (InterruptedException unused) {
+                        // this is not critical and does not require logging
+                    }
+                }
             } catch (Exception e) {
                 // Failure to vibrate shouldn't interrupt shutdown.  Just log it.
                 Log.w(TAG, "Failed to vibrate during shutdown.", e);
             }
 
-            // vibrator is asynchronous so we need to wait to avoid shutting down too soon.
-            try {
-                Thread.sleep(SHUTDOWN_VIBRATE_MS);
-            } catch (InterruptedException unused) {
-            }
         }
         // Shutdown power
         Log.i(TAG, "Performing low-level shutdown...");
diff --git a/services/core/java/com/android/server/power/stats/CpuWakeupStats.java b/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java
similarity index 91%
rename from services/core/java/com/android/server/power/stats/CpuWakeupStats.java
rename to services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java
index 231ffc6..1d63489 100644
--- a/services/core/java/com/android/server/power/stats/CpuWakeupStats.java
+++ b/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java
@@ -14,9 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.wakeups;
 
 import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_ALARM;
+import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER;
 import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_UNKNOWN;
 import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_WIFI;
 
@@ -55,7 +56,8 @@
     private static final String TAG = "CpuWakeupStats";
 
     private static final String SUBSYSTEM_ALARM_STRING = "Alarm";
-    private static final String SUBSYSTEM_ALARM_WIFI = "Wifi";
+    private static final String SUBSYSTEM_WIFI_STRING = "Wifi";
+    private static final String SUBSYSTEM_SOUND_TRIGGER_STRING = "Sound_trigger";
     private static final String TRACE_TRACK_WAKEUP_ATTRIBUTION = "wakeup_attribution";
     @VisibleForTesting
     static final long WAKEUP_REASON_HALF_WINDOW_MS = 500;
@@ -91,12 +93,24 @@
         mConfig.register(new HandlerExecutor(mHandler));
     }
 
+    private static int typeToStatsType(int wakeupType) {
+        switch (wakeupType) {
+            case Wakeup.TYPE_ABNORMAL:
+                return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__TYPE__TYPE_ABNORMAL;
+            case Wakeup.TYPE_IRQ:
+                return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__TYPE__TYPE_IRQ;
+        }
+        return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__TYPE__TYPE_UNKNOWN;
+    }
+
     private static int subsystemToStatsReason(int subsystem) {
         switch (subsystem) {
             case CPU_WAKEUP_SUBSYSTEM_ALARM:
                 return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__ALARM;
             case CPU_WAKEUP_SUBSYSTEM_WIFI:
                 return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__WIFI;
+            case CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER:
+                return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__SOUND_TRIGGER;
         }
         return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__UNKNOWN;
     }
@@ -144,7 +158,7 @@
                 }
             }
             FrameworkStatsLog.write(FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED,
-                    FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__TYPE__TYPE_IRQ,
+                    typeToStatsType(wakeupToLog.mType),
                     subsystemToStatsReason(subsystem),
                     uids,
                     wakeupToLog.mElapsedMillis,
@@ -524,8 +538,10 @@
         switch (rawSubsystem) {
             case SUBSYSTEM_ALARM_STRING:
                 return CPU_WAKEUP_SUBSYSTEM_ALARM;
-            case SUBSYSTEM_ALARM_WIFI:
+            case SUBSYSTEM_WIFI_STRING:
                 return CPU_WAKEUP_SUBSYSTEM_WIFI;
+            case SUBSYSTEM_SOUND_TRIGGER_STRING:
+                return CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER;
         }
         return CPU_WAKEUP_SUBSYSTEM_UNKNOWN;
     }
@@ -535,25 +551,43 @@
             case CPU_WAKEUP_SUBSYSTEM_ALARM:
                 return SUBSYSTEM_ALARM_STRING;
             case CPU_WAKEUP_SUBSYSTEM_WIFI:
-                return SUBSYSTEM_ALARM_WIFI;
+                return SUBSYSTEM_WIFI_STRING;
+            case CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER:
+                return SUBSYSTEM_SOUND_TRIGGER_STRING;
             case CPU_WAKEUP_SUBSYSTEM_UNKNOWN:
                 return "Unknown";
         }
         return "N/A";
     }
 
-    private static final class Wakeup {
+    @VisibleForTesting
+    static final class Wakeup {
         private static final String PARSER_TAG = "CpuWakeupStats.Wakeup";
         private static final String ABORT_REASON_PREFIX = "Abort";
-        private static final Pattern sIrqPattern = Pattern.compile("^(\\d+)\\s+(\\S+)");
+        private static final Pattern sIrqPattern = Pattern.compile("^(\\-?\\d+)\\s+(\\S+)");
+
+        /**
+         * Classical interrupts, which arrive on a dedicated GPIO pin into the main CPU.
+         * Sometimes, when multiple IRQs happen close to each other, they may get batched together.
+         */
+        static final int TYPE_IRQ = 1;
+
+        /**
+         * Non-IRQ wakeups. The exact mechanism for these is unknown, except that these explicitly
+         * do not use an interrupt line or a GPIO pin.
+         */
+        static final int TYPE_ABNORMAL = 2;
+
+        int mType;
         long mElapsedMillis;
         long mUptimeMillis;
         IrqDevice[] mDevices;
 
-        private Wakeup(IrqDevice[] devices, long elapsedMillis, long uptimeMillis) {
+        private Wakeup(int type, IrqDevice[] devices, long elapsedMillis, long uptimeMillis) {
+            mType = type;
+            mDevices = devices;
             mElapsedMillis = elapsedMillis;
             mUptimeMillis = uptimeMillis;
-            mDevices = devices;
         }
 
         static Wakeup parseWakeup(String rawReason, long elapsedMillis, long uptimeMillis) {
@@ -563,6 +597,7 @@
                 return null;
             }
 
+            int type = TYPE_IRQ;
             int parsedDeviceCount = 0;
             final IrqDevice[] parsedDevices = new IrqDevice[components.length];
 
@@ -574,6 +609,10 @@
                     try {
                         line = Integer.parseInt(matcher.group(1));
                         device = matcher.group(2);
+                        if (line < 0) {
+                            // Assuming that IRQ wakeups cannot come batched with non-IRQ wakeups.
+                            type = TYPE_ABNORMAL;
+                        }
                     } catch (NumberFormatException e) {
                         Slog.e(PARSER_TAG,
                                 "Exception while parsing device names from part: " + component, e);
@@ -585,15 +624,16 @@
             if (parsedDeviceCount == 0) {
                 return null;
             }
-            return new Wakeup(Arrays.copyOf(parsedDevices, parsedDeviceCount), elapsedMillis,
+            return new Wakeup(type, Arrays.copyOf(parsedDevices, parsedDeviceCount), elapsedMillis,
                     uptimeMillis);
         }
 
         @Override
         public String toString() {
             return "Wakeup{"
-                    + "mElapsedMillis=" + mElapsedMillis
-                    + ", mUptimeMillis=" + TimeUtils.formatDuration(mUptimeMillis)
+                    + "mType=" + mType
+                    + ", mElapsedMillis=" + mElapsedMillis
+                    + ", mUptimeMillis=" + mUptimeMillis
                     + ", mDevices=" + Arrays.toString(mDevices)
                     + '}';
         }
diff --git a/services/core/java/com/android/server/power/stats/IrqDeviceMap.java b/services/core/java/com/android/server/power/stats/wakeups/IrqDeviceMap.java
similarity index 98%
rename from services/core/java/com/android/server/power/stats/IrqDeviceMap.java
rename to services/core/java/com/android/server/power/stats/wakeups/IrqDeviceMap.java
index 091d18e..8644f72 100644
--- a/services/core/java/com/android/server/power/stats/IrqDeviceMap.java
+++ b/services/core/java/com/android/server/power/stats/wakeups/IrqDeviceMap.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.wakeups;
 
 import android.annotation.XmlRes;
 import android.content.Context;
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 7beb1ed..e437be8 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -105,36 +105,46 @@
     @Override
     public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage,
             @FailureReasons int failureReason, int mitigationCount) {
-        // For native crashes, we will roll back any available rollbacks
+        boolean anyRollbackAvailable = !mContext.getSystemService(RollbackManager.class)
+                .getAvailableRollbacks().isEmpty();
+        int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
+
         if (failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH
-                && !mContext.getSystemService(RollbackManager.class)
-                .getAvailableRollbacks().isEmpty()) {
-            return PackageHealthObserverImpact.USER_IMPACT_MEDIUM;
-        }
-        if (getAvailableRollback(failedPackage) == null) {
-            // Don't handle the notification, no rollbacks available for the package
-            return PackageHealthObserverImpact.USER_IMPACT_NONE;
-        } else {
+                && anyRollbackAvailable) {
+            // For native crashes, we will directly roll back any available rollbacks
+            // Note: For non-native crashes the rollback-all step has higher impact
+            impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
+        } else if (mitigationCount == 1 && getAvailableRollback(failedPackage) != null) {
             // Rollback is available, we may get a callback into #execute
-            return PackageHealthObserverImpact.USER_IMPACT_MEDIUM;
+            impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
+        } else if (mitigationCount > 1 && anyRollbackAvailable) {
+            // If any rollbacks are available, we will commit them
+            impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70;
         }
+
+        return impact;
     }
 
     @Override
     public boolean execute(@Nullable VersionedPackage failedPackage,
             @FailureReasons int rollbackReason, int mitigationCount) {
         if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
-            mHandler.post(() -> rollbackAll());
+            mHandler.post(() -> rollbackAll(rollbackReason));
             return true;
         }
 
-        RollbackInfo rollback = getAvailableRollback(failedPackage);
-        if (rollback == null) {
-            Slog.w(TAG, "Expected rollback but no valid rollback found for " + failedPackage);
-            return false;
+        if (mitigationCount == 1) {
+            RollbackInfo rollback = getAvailableRollback(failedPackage);
+            if (rollback == null) {
+                Slog.w(TAG, "Expected rollback but no valid rollback found for " + failedPackage);
+                return false;
+            }
+            mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason));
+        } else if (mitigationCount > 1) {
+            mHandler.post(() -> rollbackAll(rollbackReason));
         }
-        mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason));
-        // Assume rollback executed successfully
+
+        // Assume rollbacks executed successfully
         return true;
     }
 
@@ -468,7 +478,7 @@
     }
 
     @WorkerThread
-    private void rollbackAll() {
+    private void rollbackAll(@FailureReasons int rollbackReason) {
         assertInWorkerThread();
         RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
         List<RollbackInfo> rollbacks = rollbackManager.getAvailableRollbacks();
@@ -487,7 +497,7 @@
 
         for (RollbackInfo rollback : rollbacks) {
             VersionedPackage sample = rollback.getPackages().get(0).getVersionRolledBackFrom();
-            rollbackPackage(rollback, sample, PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
+            rollbackPackage(rollback, sample, rollbackReason);
         }
     }
 }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java
index 584fbdd..3699557 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java
@@ -27,4 +27,10 @@
      * Notifies the display is ready for adding wallpaper on it.
      */
     public abstract void onDisplayReady(int displayId);
+
+    /** Notifies when the screen finished turning on and is visible to the user. */
+    public abstract void onScreenTurnedOn(int displayId);
+
+    /** Notifies when the screen starts turning on and is not yet visible to the user. */
+    public abstract void onScreenTurningOn(int displayId);
 }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 4a03628..e178669 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1613,6 +1613,15 @@
         public void onDisplayReady(int displayId) {
             onDisplayReadyInternal(displayId);
         }
+
+        @Override
+        public void onScreenTurnedOn(int displayId) {
+            notifyScreenTurnedOn(displayId);
+        }
+        @Override
+        public void onScreenTurningOn(int displayId) {
+            notifyScreenTurningOn(displayId);
+        }
     }
 
     void initialize() {
@@ -2442,6 +2451,54 @@
         }
     }
 
+    /**
+     * Propagates screen turned on event to wallpaper engine.
+     */
+    @Override
+    public void notifyScreenTurnedOn(int displayId) {
+        synchronized (mLock) {
+            final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
+            if (data != null
+                    && data.connection != null
+                    && data.connection.containsDisplay(displayId)) {
+                final IWallpaperEngine engine = data.connection
+                        .getDisplayConnectorOrCreate(displayId).mEngine;
+                if (engine != null) {
+                    try {
+                        engine.onScreenTurnedOn();
+                    } catch (RemoteException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }
+    }
+
+
+
+    /**
+     * Propagate screen turning on event to wallpaper engine.
+     */
+    @Override
+    public void notifyScreenTurningOn(int displayId) {
+        synchronized (mLock) {
+            final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
+            if (data != null
+                    && data.connection != null
+                    && data.connection.containsDisplay(displayId)) {
+                final IWallpaperEngine engine = data.connection
+                        .getDisplayConnectorOrCreate(displayId).mEngine;
+                if (engine != null) {
+                    try {
+                        engine.onScreenTurningOn();
+                    } catch (RemoteException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }
+    }
+
     @Override
     public boolean setLockWallpaperCallback(IWallpaperManagerCallback cb) {
         checkPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW);
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index df360b8..f300113 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -25,6 +25,8 @@
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
 
+import static com.android.internal.util.DumpUtils.dumpSparseArray;
+import static com.android.internal.util.DumpUtils.dumpSparseArrayValues;
 import static com.android.server.accessibility.AccessibilityTraceFileProto.ENTRY;
 import static com.android.server.accessibility.AccessibilityTraceFileProto.MAGIC_NUMBER;
 import static com.android.server.accessibility.AccessibilityTraceFileProto.MAGIC_NUMBER_H;
@@ -44,8 +46,6 @@
 import static com.android.server.accessibility.AccessibilityTraceProto.WINDOW_MANAGER_SERVICE;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.dumpSparseArray;
-import static com.android.server.wm.WindowManagerService.dumpSparseArrayValues;
 import static com.android.server.wm.WindowTracing.WINSCOPE_EXT;
 
 import android.accessibilityservice.AccessibilityTrace;
diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
index afe1640..70f2007 100644
--- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
+++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
@@ -16,8 +16,9 @@
 
 package com.android.server.wm;
 
-import static com.android.server.wm.WindowManagerService.ValueDumper;
-import static com.android.server.wm.WindowManagerService.dumpSparseArray;
+import static com.android.internal.util.DumpUtils.KeyDumper;
+import static com.android.internal.util.DumpUtils.ValueDumper;
+import static com.android.internal.util.DumpUtils.dumpSparseArray;
 import static com.android.server.wm.utils.RegionUtils.forEachRect;
 
 import android.annotation.NonNull;
@@ -41,7 +42,6 @@
 import android.window.WindowInfosListener;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.server.wm.WindowManagerService.KeyDumper;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 6214440..8bbcd27 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -97,7 +97,6 @@
 
 import com.android.internal.app.AssistUtils;
 import com.android.internal.policy.IKeyguardDismissCallback;
-import com.android.internal.protolog.ProtoLogGroup;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.LocalServices;
 import com.android.server.Watchdog;
@@ -1142,19 +1141,11 @@
         // Initiate the transition.
         final Transition transition = new Transition(TRANSIT_CHANGE, 0 /* flags */, controller,
                 mService.mWindowManager.mSyncEngine);
-        if (mService.mWindowManager.mSyncEngine.hasActiveSync()) {
-            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
-                    "Creating Pending Multiwindow Fullscreen Request: %s", transition);
-            mService.mWindowManager.mSyncEngine.queueSyncSet(
-                    () -> r.mTransitionController.moveToCollecting(transition),
-                    () -> {
-                        executeFullscreenRequestTransition(fullscreenRequest, callback, r,
-                                transition, true /* queued */);
-                    });
-        } else {
-            executeFullscreenRequestTransition(fullscreenRequest, callback, r, transition,
-                    false /* queued */);
-        }
+        r.mTransitionController.startCollectOrQueue(transition,
+                (deferred) -> {
+                    executeFullscreenRequestTransition(fullscreenRequest, callback, r,
+                            transition, deferred);
+                });
     }
 
     private void executeFullscreenRequestTransition(int fullscreenRequest, IRemoteCallback callback,
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 1bcc05e..64e1ae5 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2865,11 +2865,9 @@
             return;
         }
 
-        if (animate && mTransitionController.inCollectingTransition(startingWindow)
-                && startingWindow.cancelAndRedraw()) {
+        if (animate && mTransitionController.inCollectingTransition(startingWindow)) {
             // Defer remove starting window after transition start.
-            // If splash screen window was in collecting, the client side is unable to draw because
-            // of Session#cancelDraw, which will blocking the remove animation.
+            // The surface of app window could really show after the transition finish.
             startingWindow.mSyncTransaction.addTransactionCommittedListener(Runnable::run, () -> {
                 synchronized (mAtmService.mGlobalLock) {
                     surface.remove(true);
@@ -9806,7 +9804,7 @@
         if (mTransitionController.isShellTransitionsEnabled()) {
             final Transition transition = new Transition(TRANSIT_RELAUNCH, 0 /* flags */,
                     mTransitionController, mWmService.mSyncEngine);
-            final Runnable executeRestart = () -> {
+            mTransitionController.startCollectOrQueue(transition, (deferred) -> {
                 if (mState != RESTARTING_PROCESS || !attachedToProcess()) {
                     transition.abort();
                     return;
@@ -9818,14 +9816,7 @@
                 mTransitionController.requestStartTransition(transition, task,
                         null /* remoteTransition */, null /* displayChange */);
                 scheduleStopForRestartProcess();
-            };
-            if (mWmService.mSyncEngine.hasActiveSync()) {
-                mWmService.mSyncEngine.queueSyncSet(
-                        () -> mTransitionController.moveToCollecting(transition), executeRestart);
-            } else {
-                mTransitionController.moveToCollecting(transition);
-                executeRestart.run();
-            }
+            });
         } else {
             startFreezingScreen();
             scheduleStopForRestartProcess();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 12fe6a0..064af0f 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -250,7 +250,6 @@
 import com.android.internal.os.TransferPipe;
 import com.android.internal.policy.AttributeCache;
 import com.android.internal.policy.KeyguardDismissCallback;
-import com.android.internal.protolog.ProtoLogGroup;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastPrintWriter;
@@ -831,7 +830,7 @@
     private final Runnable mUpdateOomAdjRunnable = new Runnable() {
         @Override
         public void run() {
-            mAmInternal.updateOomAdj();
+            mAmInternal.updateOomAdj(ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY);
         }
     };
 
@@ -2873,29 +2872,19 @@
 
                 final Transition transition = new Transition(TRANSIT_CHANGE, 0 /* flags */,
                         getTransitionController(), mWindowManager.mSyncEngine);
-                if (mWindowManager.mSyncEngine.hasActiveSync()) {
-                    mWindowManager.mSyncEngine.queueSyncSet(
-                            () -> getTransitionController().moveToCollecting(transition),
-                            () -> {
-                                if (!task.getWindowConfiguration().canResizeTask()) {
-                                    Slog.w(TAG, "resizeTask not allowed on task=" + task);
-                                    transition.abort();
-                                    return;
-                                }
-                                getTransitionController().requestStartTransition(transition, task,
-                                        null /* remoteTransition */, null /* displayChange */);
-                                getTransitionController().collect(task);
-                                task.resize(bounds, resizeMode, preserveWindow);
-                                transition.setReady(task, true);
-                            });
-                } else {
-                    getTransitionController().moveToCollecting(transition);
-                    getTransitionController().requestStartTransition(transition, task,
-                            null /* remoteTransition */, null /* displayChange */);
-                    getTransitionController().collect(task);
-                    task.resize(bounds, resizeMode, preserveWindow);
-                    transition.setReady(task, true);
-                }
+                getTransitionController().startCollectOrQueue(transition,
+                        (deferred) -> {
+                            if (deferred && !task.getWindowConfiguration().canResizeTask()) {
+                                Slog.w(TAG, "resizeTask not allowed on task=" + task);
+                                transition.abort();
+                                return;
+                            }
+                            getTransitionController().requestStartTransition(transition, task,
+                                    null /* remoteTransition */, null /* displayChange */);
+                            getTransitionController().collect(task);
+                            task.resize(bounds, resizeMode, preserveWindow);
+                            transition.setReady(task, true);
+                        });
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -3626,34 +3615,25 @@
             mActivityClientController.dismissKeyguard(r.token, new KeyguardDismissCallback() {
                 @Override
                 public void onDismissSucceeded() {
-                    if (transition != null && mWindowManager.mSyncEngine.hasActiveSync()) {
-                        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
-                                "Creating Pending Pip-Enter: %s", transition);
-                        mWindowManager.mSyncEngine.queueSyncSet(
-                                () -> getTransitionController().moveToCollecting(transition),
-                                enterPipRunnable);
-                    } else {
-                        // Move to collecting immediately to "claim" the sync-engine for this
-                        // transition.
-                        if (transition != null) {
-                            getTransitionController().moveToCollecting(transition);
-                        }
+                    if (transition == null) {
                         mH.post(enterPipRunnable);
+                        return;
                     }
+                    getTransitionController().startCollectOrQueue(transition, (deferred) -> {
+                        if (deferred) {
+                            enterPipRunnable.run();
+                        } else {
+                            mH.post(enterPipRunnable);
+                        }
+                    });
                 }
             }, null /* message */);
         } else {
             // Enter picture in picture immediately otherwise
-            if (transition != null && mWindowManager.mSyncEngine.hasActiveSync()) {
-                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
-                        "Creating Pending Pip-Enter: %s", transition);
-                mWindowManager.mSyncEngine.queueSyncSet(
-                        () -> getTransitionController().moveToCollecting(transition),
-                        enterPipRunnable);
+            if (transition != null) {
+                getTransitionController().startCollectOrQueue(transition,
+                        (deferred) -> enterPipRunnable.run());
             } else {
-                if (transition != null) {
-                    getTransitionController().moveToCollecting(transition);
-                }
                 enterPipRunnable.run();
             }
         }
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 7b562b0..11d84ff 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -532,14 +532,7 @@
             if (newFocus != null && newFocus != mNavigatingWindow
                     && (newFocus.mActivityRecord == null
                     || (newFocus.mActivityRecord == mNavigatingWindow.mActivityRecord))) {
-                EventLogTags.writeWmBackNaviCanceled("focusWindowChanged");
-                if (isMonitorForRemote()) {
-                    mObserver.sendResult(null /* result */);
-                }
-                if (isMonitorAnimationOrTransition()) {
-                    // transition won't happen, cancel internal status
-                    clearBackAnimations();
-                }
+                cancelBackNavigating("focusWindowChanged");
             }
         }
 
@@ -553,19 +546,12 @@
             }
             final ArrayList<WindowContainer> all = new ArrayList<>(opening);
             all.addAll(closing);
-            for (WindowContainer app : all) {
-                if (app.hasChild(mNavigatingWindow)) {
-                    EventLogTags.writeWmBackNaviCanceled("transitionHappens");
-                    if (isMonitorForRemote()) {
-                        mObserver.sendResult(null /* result */);
-                    }
-                    if (isMonitorAnimationOrTransition()) {
-                        clearBackAnimations();
-                    }
+            for (int i = all.size() - 1; i >= 0; --i) {
+                if (all.get(i).hasChild(mNavigatingWindow)) {
+                    cancelBackNavigating("transitionHappens");
                     break;
                 }
             }
-
         }
 
         private boolean atSameDisplay(WindowState newFocus) {
@@ -575,6 +561,17 @@
             final int navigatingDisplayId = mNavigatingWindow.getDisplayId();
             return newFocus == null || newFocus.getDisplayId() == navigatingDisplayId;
         }
+
+        private void cancelBackNavigating(String reason) {
+            EventLogTags.writeWmBackNaviCanceled(reason);
+            if (isMonitorForRemote()) {
+                mObserver.sendResult(null /* result */);
+            }
+            if (isMonitorAnimationOrTransition()) {
+                clearBackAnimations();
+            }
+            cancelPendingAnimation();
+        }
     }
 
     // For shell transition
@@ -648,31 +645,28 @@
         if (finishedTransition == mWaitTransitionFinish) {
             clearBackAnimations();
         }
+
         if (!mBackAnimationInProgress || mPendingAnimationBuilder == null) {
             return false;
         }
-
         ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
                 "Handling the deferred animation after transition finished");
 
-        // Show the target surface and its parents to prevent it or its parents hidden when
-        // the transition finished.
-        // The target could be affected by transition when :
-        // Open transition -> the open target in back navigation
-        // Close transition -> the close target in back navigation.
+        // Find the participated container collected by transition when :
+        // Open transition -> the open target in back navigation, the close target in transition.
+        // Close transition -> the close target in back navigation, the open target in transition.
         boolean hasTarget = false;
-        final SurfaceControl.Transaction t =
-                mPendingAnimationBuilder.mCloseTarget.getPendingTransaction();
-        for (int i = 0; i < targets.size(); i++) {
-            final WindowContainer wc = targets.get(i).mContainer;
-            if (wc.asActivityRecord() == null && wc.asTask() == null) {
-                continue;
-            } else if (!mPendingAnimationBuilder.containTarget(wc)) {
+        for (int i = 0; i < finishedTransition.mParticipants.size(); i++) {
+            final WindowContainer wc = finishedTransition.mParticipants.valueAt(i);
+            if (wc.asActivityRecord() == null && wc.asTask() == null
+                    && wc.asTaskFragment() == null) {
                 continue;
             }
 
-            hasTarget = true;
-            t.show(wc.getSurfaceControl());
+            if (mPendingAnimationBuilder.containTarget(wc)) {
+                hasTarget = true;
+                break;
+            }
         }
 
         if (!hasTarget) {
@@ -680,20 +674,33 @@
             Slog.w(TAG, "Finished transition didn't include the targets"
                     + " open: " + mPendingAnimationBuilder.mOpenTarget
                     + " close: " + mPendingAnimationBuilder.mCloseTarget);
-            try {
-                mPendingAnimationBuilder.mBackAnimationAdapter.getRunner().onAnimationCancelled();
-            } catch (RemoteException e) {
-                throw new RuntimeException(e);
-            }
-            mPendingAnimationBuilder = null;
+            cancelPendingAnimation();
             return false;
         }
 
+        // Ensure the final animation targets which hidden by transition could be visible.
+        for (int i = 0; i < targets.size(); i++) {
+            final WindowContainer wc = targets.get(i).mContainer;
+            wc.prepareSurfaces();
+        }
+
         scheduleAnimation(mPendingAnimationBuilder);
         mPendingAnimationBuilder = null;
         return true;
     }
 
+    private void cancelPendingAnimation() {
+        if (mPendingAnimationBuilder == null) {
+            return;
+        }
+        try {
+            mPendingAnimationBuilder.mBackAnimationAdapter.getRunner().onAnimationCancelled();
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote animation gone", e);
+        }
+        mPendingAnimationBuilder = null;
+    }
+
     /**
      * Create and handling animations status for an open/close animation targets.
      */
@@ -1076,7 +1083,7 @@
 
             boolean containTarget(@NonNull WindowContainer wc) {
                 return wc == mOpenTarget || wc == mCloseTarget
-                        || wc.hasChild(mOpenTarget) || wc.hasChild(mCloseTarget);
+                        || mOpenTarget.hasChild(wc) || mCloseTarget.hasChild(wc);
             }
 
             /**
@@ -1151,6 +1158,11 @@
     private static void setLaunchBehind(@NonNull ActivityRecord activity) {
         if (!activity.isVisibleRequested()) {
             activity.setVisibility(true);
+            // The transition could commit the visibility and in the finishing state, that could
+            // skip commitVisibility call in setVisibility cause the activity won't visible here.
+            // Call it again to make sure the activity could be visible while handling the pending
+            // animation.
+            activity.commitVisibility(true, true);
         }
         activity.mLaunchTaskBehind = true;
 
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index c6db8a7..8660bec 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -263,16 +263,19 @@
      * {@link WindowContainer#prepareSurfaces}. After calling this, the container should
      * chain {@link WindowContainer#prepareSurfaces} down to it's children to give them
      * a chance to request dims to continue.
-     * @return Non-null dim bounds if the dimmer is showing.
      */
-    Rect resetDimStates() {
+    void resetDimStates() {
         if (mDimState == null) {
-            return null;
+            return;
         }
         if (!mDimState.mDontReset) {
             mDimState.mDimming = false;
         }
-        return mDimState.mDimBounds;
+    }
+
+    /** Returns non-null bounds if the dimmer is showing. */
+    Rect getDimBounds() {
+        return mDimState != null ? mDimState.mDimBounds : null;
     }
 
     void dontAnimateExit() {
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 26f56a2..9f59f5a 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -779,8 +779,9 @@
 
         @Override
         void prepareSurfaces() {
-            final Rect dimBounds = mDimmer.resetDimStates();
+            mDimmer.resetDimStates();
             super.prepareSurfaces();
+            final Rect dimBounds = mDimmer.getDimBounds();
             if (dimBounds != null) {
                 // Bounds need to be relative, as the dim layer is a child.
                 getBounds(dimBounds);
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 57fca3ae..2378469 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -146,8 +146,6 @@
 
     @VisibleForTesting
     boolean mIsAddingTaskToTargets;
-    @VisibleForTesting
-    boolean mShouldAttachNavBarToAppDuringTransition;
     private boolean mNavigationBarAttachedToApp;
     private ActivityRecord mNavBarAttachedApp;
 
@@ -379,8 +377,6 @@
         mDisplayId = displayId;
         mStatusBar = LocalServices.getService(StatusBarManagerInternal.class);
         mDisplayContent = service.mRoot.getDisplayContent(displayId);
-        mShouldAttachNavBarToAppDuringTransition =
-                mDisplayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition();
     }
 
     /**
@@ -577,7 +573,7 @@
     }
 
     private void attachNavigationBarToApp() {
-        if (!mShouldAttachNavBarToAppDuringTransition
+        if (!mDisplayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
                 // Skip the case where the nav bar is controlled by fade rotation.
                 || mDisplayContent.getAsyncRotationController() != null) {
             return;
@@ -652,7 +648,7 @@
     }
 
     void animateNavigationBarForAppLaunch(long duration) {
-        if (!mShouldAttachNavBarToAppDuringTransition
+        if (!mDisplayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
                 // Skip the case where the nav bar is controlled by fade rotation.
                 || mDisplayContent.getAsyncRotationController() != null
                 || mNavigationBarAttachedToApp
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 07daa4b..cd4b3c5 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2328,19 +2328,23 @@
                 // this as a signal to the transition-player.
                 final Transition transition = new Transition(TRANSIT_SLEEP, 0 /* flags */,
                         display.mTransitionController, mWmService.mSyncEngine);
-                final Runnable sendSleepTransition = () -> {
+                final TransitionController.OnStartCollect sendSleepTransition = (deferred) -> {
                     display.mTransitionController.requestStartTransition(transition,
                             null /* trigger */, null /* remote */, null /* display */);
                     // Force playing immediately so that unrelated ops can't be collected.
                     transition.playNow();
                 };
-                if (display.mTransitionController.isCollecting()) {
-                    mWmService.mSyncEngine.queueSyncSet(
-                            () -> display.mTransitionController.moveToCollecting(transition),
-                            sendSleepTransition);
-                } else {
+                if (!display.mTransitionController.isCollecting()) {
+                    // Since this bypasses sync, submit directly ignoring whether sync-engine
+                    // is active.
+                    if (mWindowManager.mSyncEngine.hasActiveSync()) {
+                        Slog.w(TAG, "Ongoing sync outside of a transition.");
+                    }
                     display.mTransitionController.moveToCollecting(transition);
-                    sendSleepTransition.run();
+                    sendSleepTransition.onCollectStarted(false /* deferred */);
+                } else {
+                    display.mTransitionController.startCollectOrQueue(transition,
+                            sendSleepTransition);
                 }
             }
 
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index fb592e1..9363eb5 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -188,7 +188,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IVoiceInteractor;
-import com.android.internal.protolog.ProtoLogGroup;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.XmlUtils;
 import com.android.internal.util.function.pooled.PooledLambda;
@@ -3253,9 +3252,10 @@
 
     @Override
     void prepareSurfaces() {
-        final Rect dimBounds = mDimmer.resetDimStates();
+        mDimmer.resetDimStates();
         super.prepareSurfaces();
 
+        final Rect dimBounds = mDimmer.getDimBounds();
         if (dimBounds != null) {
             getDimBounds(dimBounds);
 
@@ -5632,30 +5632,20 @@
             final Transition transition = new Transition(TRANSIT_TO_BACK, 0 /* flags */,
                     mTransitionController, mWmService.mSyncEngine);
             // Guarantee that this gets its own transition by queueing on SyncEngine
-            if (mWmService.mSyncEngine.hasActiveSync()) {
-                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
-                        "Creating Pending Move-to-back: %s", transition);
-                mWmService.mSyncEngine.queueSyncSet(
-                        () -> mTransitionController.moveToCollecting(transition),
-                        () -> {
-                            // Need to check again since this happens later and the system might
-                            // be in a different state.
-                            if (!canMoveTaskToBack(tr)) {
-                                Slog.e(TAG, "Failed to move task to back after saying we could: "
-                                        + tr.mTaskId);
-                                transition.abort();
-                                return;
-                            }
-                            mTransitionController.requestStartTransition(transition, tr,
-                                    null /* remoteTransition */, null /* displayChange */);
-                            moveTaskToBackInner(tr);
-                        });
-            } else {
-                mTransitionController.moveToCollecting(transition);
-                mTransitionController.requestStartTransition(transition, tr,
-                        null /* remoteTransition */, null /* displayChange */);
-                moveTaskToBackInner(tr);
-            }
+            mTransitionController.startCollectOrQueue(transition,
+                    (deferred) -> {
+                        // Need to check again if deferred since the system might
+                        // be in a different state.
+                        if (deferred && !canMoveTaskToBack(tr)) {
+                            Slog.e(TAG, "Failed to move task to back after saying we could: "
+                                    + tr.mTaskId);
+                            transition.abort();
+                            return;
+                        }
+                        mTransitionController.requestStartTransition(transition, tr,
+                                null /* remoteTransition */, null /* displayChange */);
+                        moveTaskToBackInner(tr);
+                    });
         } else {
             // Skip the transition for pinned task.
             if (!inPinnedWindowingMode()) {
@@ -5684,6 +5674,7 @@
             // Usually resuming a top activity triggers the next app transition, but nothing's got
             // resumed in this case, so we need to execute it explicitly.
             mDisplayContent.executeAppTransition();
+            mDisplayContent.setFocusedApp(topActivity);
         } else {
             mRootWindowContainer.resumeFocusedTasksTopActivities();
         }
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 1d232fe..311b9a6 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2923,9 +2923,10 @@
             return;
         }
 
-        final Rect dimBounds = mDimmer.resetDimStates();
+        mDimmer.resetDimStates();
         super.prepareSurfaces();
 
+        final Rect dimBounds = mDimmer.getDimBounds();
         if (dimBounds != null) {
             // Bounds need to be relative, as the dim layer is a child.
             dimBounds.offsetTo(0 /* newLeft */, 0 /* newTop */);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 3a909ce..76b0e7b 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -287,7 +287,7 @@
 
         if (restoreBelow != null) {
             final Task transientRootTask = activity.getRootTask();
-            // Collect all visible activities which can be occluded by the transient activity to
+            // Collect all visible tasks which can be occluded by the transient activity to
             // make sure they are in the participants so their visibilities can be updated when
             // finishing transition.
             ((WindowContainer<?>) restoreBelow.getParent()).forAllTasks(t -> {
@@ -297,11 +297,7 @@
                         mTransientHideTasks.add(t);
                     }
                     if (t.isLeafTask()) {
-                        t.forAllActivities(r -> {
-                            if (r.isVisibleRequested()) {
-                                collect(r);
-                            }
-                        });
+                        collect(t);
                     }
                 }
                 return t == restoreBelow;
@@ -904,6 +900,18 @@
         mController.mFinishingTransition = this;
 
         if (mTransientHideTasks != null && !mTransientHideTasks.isEmpty()) {
+            // Record all the now-hiding activities so that they are committed after
+            // recalculating visibilities. We just use mParticipants because we can and it will
+            // ensure proper reporting of `isInFinishTransition`.
+            for (int i = 0; i < mTransientHideTasks.size(); ++i) {
+                mTransientHideTasks.get(i).forAllActivities(r -> {
+                    // Only check leaf-tasks that were collected
+                    if (!mParticipants.contains(r.getTask())) return;
+                    // Only concern ourselves with anything that can become invisible
+                    if (!r.isVisible()) return;
+                    mParticipants.add(r);
+                });
+            }
             // The transient hide tasks could be occluded now, e.g. returning to home. So trigger
             // the update to make the activities in the tasks invisible-requested, then the next
             // step can continue to commit the visibility.
@@ -953,7 +961,10 @@
                         enterAutoPip = true;
                     }
                 }
-                if (mChanges.get(ar).mVisible != visibleAtTransitionEnd) {
+                final ChangeInfo changeInfo = mChanges.get(ar);
+                // Due to transient-hide, there may be some activities here which weren't in the
+                // transition.
+                if (changeInfo != null && changeInfo.mVisible != visibleAtTransitionEnd) {
                     // Legacy dispatch relies on this (for now).
                     ar.mEnteringAnimation = visibleAtTransitionEnd;
                 } else if (mTransientLaunches != null && mTransientLaunches.containsKey(ar)
@@ -1609,7 +1620,7 @@
         sb.append(Integer.toHexString(System.identityHashCode(this)));
         sb.append(" id=" + mSyncId);
         sb.append(" type=" + transitTypeToString(mType));
-        sb.append(" flags=" + mFlags);
+        sb.append(" flags=0x" + Integer.toHexString(mFlags));
         sb.append('}');
         return sb.toString();
     }
@@ -2824,7 +2835,7 @@
             final String name = isDisplayRotation ? "RotationLayer" : "transition snapshot: " + wc;
             SurfaceControl snapshotSurface = wc.makeAnimationLeash()
                     .setName(name)
-                    .setOpaque(true)
+                    .setOpaque(wc.fillsParent())
                     .setParent(wc.getSurfaceControl())
                     .setSecure(screenshotBuffer.containsSecureLayers())
                     .setCallsite("Transition.ScreenshotSync")
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 3bf8969..cbb4fe2 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -443,26 +443,36 @@
         return type == TRANSIT_OPEN || type == TRANSIT_CLOSE;
     }
 
-    /** Whether the display change should run with blast sync. */
-    private static boolean shouldSync(@NonNull TransitionRequestInfo.DisplayChange displayChange) {
-        if ((displayChange.getStartRotation() + displayChange.getEndRotation()) % 2 == 0) {
+    /** Sets the sync method for the display change. */
+    private void setDisplaySyncMethod(@NonNull TransitionRequestInfo.DisplayChange displayChange,
+            @NonNull Transition displayTransition, @NonNull DisplayContent displayContent) {
+        final int startRotation = displayChange.getStartRotation();
+        final int endRotation = displayChange.getEndRotation();
+        if (startRotation != endRotation && (startRotation + endRotation) % 2 == 0) {
             // 180 degrees rotation change may not change screen size. So the clients may draw
             // some frames before and after the display projection transaction is applied by the
             // remote player. That may cause some buffers to show in different rotation. So use
             // sync method to pause clients drawing until the projection transaction is applied.
-            return true;
+            mAtm.mWindowManager.mSyncEngine.setSyncMethod(displayTransition.getSyncId(),
+                    BLASTSyncEngine.METHOD_BLAST);
         }
         final Rect startBounds = displayChange.getStartAbsBounds();
         final Rect endBounds = displayChange.getEndAbsBounds();
-        if (startBounds == null || endBounds == null) return false;
+        if (startBounds == null || endBounds == null) return;
         final int startWidth = startBounds.width();
         final int startHeight = startBounds.height();
         final int endWidth = endBounds.width();
         final int endHeight = endBounds.height();
         // This is changing screen resolution. Because the screen decor layers are excluded from
         // screenshot, their draw transactions need to run with the start transaction.
-        return (endWidth > startWidth) == (endHeight > startHeight)
-                && (endWidth != startWidth || endHeight != startHeight);
+        if ((endWidth > startWidth) == (endHeight > startHeight)
+                && (endWidth != startWidth || endHeight != startHeight)) {
+            displayContent.forAllWindows(w -> {
+                if (w.mToken.mRoundedCornerOverlay && w.mHasSurface) {
+                    w.mSyncMethodOverride = BLASTSyncEngine.METHOD_BLAST;
+                }
+            }, true /* traverseTopToBottom */);
+        }
     }
 
     /**
@@ -494,9 +504,9 @@
         } else {
             newTransition = requestStartTransition(createTransition(type, flags),
                     trigger != null ? trigger.asTask() : null, remoteTransition, displayChange);
-            if (newTransition != null && displayChange != null && shouldSync(displayChange)) {
-                mAtm.mWindowManager.mSyncEngine.setSyncMethod(newTransition.getSyncId(),
-                        BLASTSyncEngine.METHOD_BLAST);
+            if (newTransition != null && displayChange != null && trigger != null
+                    && trigger.asDisplayContent() != null) {
+                setDisplaySyncMethod(displayChange, newTransition, trigger.asDisplayContent());
             }
         }
         if (trigger != null) {
@@ -874,6 +884,32 @@
         proto.end(token);
     }
 
+    /** Returns {@code true} if it started collecting, {@code false} if it was queued. */
+    boolean startCollectOrQueue(Transition transit, OnStartCollect onStartCollect) {
+        if (mAtm.mWindowManager.mSyncEngine.hasActiveSync()) {
+            if (!isCollecting()) {
+                Slog.w(TAG, "Ongoing Sync outside of transition.");
+            }
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN,
+                    "Queueing transition: %s", transit);
+            mAtm.mWindowManager.mSyncEngine.queueSyncSet(
+                    // Make sure to collect immediately to prevent another transition
+                    // from sneaking in before it. Note: moveToCollecting internally
+                    // calls startSyncSet.
+                    () -> moveToCollecting(transit),
+                    () -> onStartCollect.onCollectStarted(true /* deferred */));
+            return false;
+        } else {
+            moveToCollecting(transit);
+            onStartCollect.onCollectStarted(false /* deferred */);
+            return true;
+        }
+    }
+
+    interface OnStartCollect {
+        void onCollectStarted(boolean deferred);
+    }
+
     /**
      * This manages the animating state of processes that are running remote animations for
      * {@link #mTransitionPlayerProc}.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 82c057b..8fecf11 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -235,7 +235,6 @@
 import android.util.MergedConfiguration;
 import android.util.Pair;
 import android.util.Slog;
-import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
@@ -9460,53 +9459,4 @@
             return List.copyOf(notifiedApps);
         }
     }
-
-    // TODO(b/271188189): move dump stuff below to common code / add unit tests
-
-    interface ValueDumper<T> {
-        void dump(T value);
-    }
-
-    interface KeyDumper{
-        void dump(int index, int key);
-    }
-
-    static void dumpSparseArray(PrintWriter pw, String prefix, SparseArray<?> array, String name) {
-        dumpSparseArray(pw, prefix, array, name, /* keyDumper= */ null, /* valuedumper= */ null);
-    }
-
-    static <T> void dumpSparseArrayValues(PrintWriter pw, String prefix, SparseArray<T> array,
-            String name) {
-        dumpSparseArray(pw, prefix, array, name, (i, k) -> {}, /* valueDumper= */ null);
-    }
-
-    static <T> void dumpSparseArray(PrintWriter pw, String prefix, SparseArray<T> array,
-            String name, @Nullable KeyDumper keyDumper, @Nullable ValueDumper<T> valueDumper) {
-        int size = array.size();
-        if (size == 0) {
-            pw.print(prefix); pw.print("No "); pw.print(name); pw.println("s");
-            return;
-        }
-        pw.print(prefix); pw.print(size); pw.print(' ');
-        pw.print(name); pw.print(size > 1 ? "s" : ""); pw.println(':');
-
-        String prefix2 = prefix + "  ";
-        for (int i = 0; i < size; i++) {
-            int key = array.keyAt(i);
-            T value = array.valueAt(i);
-            if (keyDumper != null) {
-                keyDumper.dump(i, key);
-            } else {
-                pw.print(prefix2); pw.print(i); pw.print(": "); pw.print(key); pw.print("->");
-            }
-            if (value == null) {
-                pw.print("(null)");
-            } else if (valueDumper != null) {
-                valueDumper.dump(value);
-            } else {
-                pw.print(value);
-            }
-            pw.println();
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 32d54d7..ee86b97 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -280,43 +280,31 @@
                     applyTransaction(t, -1 /* syncId */, null, caller);
                     return null;
                 }
-                // In cases where transition is already provided, the "readiness lifecycle" of the
-                // transition is determined outside of this transaction. However, if this is a
-                // direct call from shell, the entire transition lifecycle is contained in the
-                // provided transaction and thus we can setReady immediately after apply.
-                final boolean needsSetReady = transition == null && t != null;
                 final WindowContainerTransaction wct =
                         t != null ? t : new WindowContainerTransaction();
                 if (transition == null) {
                     if (type < 0) {
                         throw new IllegalArgumentException("Can't create transition with no type");
                     }
-                    // If there is already a collecting transition, queue up a new transition and
-                    // return that. The actual start and apply will then be deferred until that
-                    // transition starts collecting. This should almost never happen except during
-                    // tests.
-                    if (mService.mWindowManager.mSyncEngine.hasActiveSync()) {
-                        Slog.w(TAG, "startTransition() while one is already collecting.");
-                        final Transition nextTransition = new Transition(type, 0 /* flags */,
-                                mTransitionController, mService.mWindowManager.mSyncEngine);
-                        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
-                                "Creating Pending Transition: %s", nextTransition);
-                        mService.mWindowManager.mSyncEngine.queueSyncSet(
-                                // Make sure to collect immediately to prevent another transition
-                                // from sneaking in before it. Note: moveToCollecting internally
-                                // calls startSyncSet.
-                                () -> mTransitionController.moveToCollecting(nextTransition),
-                                () -> {
-                                    nextTransition.start();
-                                    applyTransaction(wct, -1 /*syncId*/, nextTransition, caller);
-                                    if (needsSetReady) {
-                                        nextTransition.setAllReady();
-                                    }
-                                });
-                        return nextTransition.getToken();
-                    }
-                    transition = mTransitionController.createTransition(type);
+                    // This is a direct call from shell, so the entire transition lifecycle is
+                    // contained in the provided transaction if provided. Thus, we can setReady
+                    // immediately after apply.
+                    final boolean needsSetReady = t != null;
+                    final Transition nextTransition = new Transition(type, 0 /* flags */,
+                            mTransitionController, mService.mWindowManager.mSyncEngine);
+                    mTransitionController.startCollectOrQueue(nextTransition,
+                            (deferred) -> {
+                                nextTransition.start();
+                                nextTransition.mLogger.mStartWCT = wct;
+                                applyTransaction(wct, -1 /*syncId*/, nextTransition, caller);
+                                if (needsSetReady) {
+                                    nextTransition.setAllReady();
+                                }
+                            });
+                    return nextTransition.getToken();
                 }
+                // The transition already started collecting before sending a request to shell,
+                // so just start here.
                 if (!transition.isCollecting() && !transition.isForcePlaying()) {
                     Slog.e(TAG, "Trying to start a transition that isn't collecting. This probably"
                             + " means Shell took too long to respond to a request. WM State may be"
@@ -327,9 +315,8 @@
                 transition.start();
                 transition.mLogger.mStartWCT = wct;
                 applyTransaction(wct, -1 /*syncId*/, transition, caller);
-                if (needsSetReady) {
-                    transition.setAllReady();
-                }
+                // Since the transition is already provided, it means WMCore is determining the
+                // "readiness lifecycle" outside the provided transaction, so don't set ready here.
                 return transition.getToken();
             }
         } finally {
@@ -434,23 +421,8 @@
                 return;
             }
 
-            if (!mService.mWindowManager.mSyncEngine.hasActiveSync()) {
-                // Sync is for either transition or applySyncTransaction(). We don't support
-                // multiple sync at the same time because it may cause conflict.
-                // Create a new transition when there is no active sync to collect the changes.
-                final Transition transition = mTransitionController.createTransition(type);
-                if (applyTransaction(wct, -1 /* syncId */, transition, caller)
-                        == TRANSACT_EFFECTS_NONE && transition.mParticipants.isEmpty()) {
-                    transition.abort();
-                    return;
-                }
-                mTransitionController.requestStartTransition(transition, null /* startTask */,
-                        null /* remoteTransition */, null /* displayChange */);
-                transition.setAllReady();
-                return;
-            }
-
-            if (!shouldApplyIndependently) {
+            if (mService.mWindowManager.mSyncEngine.hasActiveSync()
+                    && !shouldApplyIndependently) {
                 // Although there is an active sync, we want to apply the transaction now.
                 // TODO(b/232042367) Redesign the organizer update on activity callback so that we
                 // we will know about the transition explicitly.
@@ -469,29 +441,23 @@
                 return;
             }
 
-            // It is ok to queue the WCT until the sync engine is free.
-            final Transition nextTransition = new Transition(type, 0 /* flags */,
+            final Transition transition = new Transition(type, 0 /* flags */,
                     mTransitionController, mService.mWindowManager.mSyncEngine);
-            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
-                    "Creating Pending Transition for TaskFragment: %s", nextTransition);
-            mService.mWindowManager.mSyncEngine.queueSyncSet(
-                    // Make sure to collect immediately to prevent another transition
-                    // from sneaking in before it. Note: moveToCollecting internally
-                    // calls startSyncSet.
-                    () -> mTransitionController.moveToCollecting(nextTransition),
-                    () -> {
-                        if (mTaskFragmentOrganizerController.isValidTransaction(wct)
-                                && (applyTransaction(wct, -1 /* syncId */, nextTransition, caller)
-                                        != TRANSACT_EFFECTS_NONE
-                                || !nextTransition.mParticipants.isEmpty())) {
-                            mTransitionController.requestStartTransition(nextTransition,
-                                    null /* startTask */, null /* remoteTransition */,
-                                    null /* displayChange */);
-                            nextTransition.setAllReady();
-                            return;
-                        }
-                        nextTransition.abort();
-                    });
+            TransitionController.OnStartCollect doApply = (deferred) -> {
+                if (deferred && !mTaskFragmentOrganizerController.isValidTransaction(wct)) {
+                    transition.abort();
+                    return;
+                }
+                if (applyTransaction(wct, -1 /* syncId */, transition, caller)
+                        == TRANSACT_EFFECTS_NONE && transition.mParticipants.isEmpty()) {
+                    transition.abort();
+                    return;
+                }
+                mTransitionController.requestStartTransition(transition, null /* startTask */,
+                        null /* remoteTransition */, null /* displayChange */);
+                transition.setAllReady();
+            };
+            mTransitionController.startCollectOrQueue(transition, doApply);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index a04143a..5a71808 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -89,14 +89,16 @@
     protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
         mRequestSessionMetric.collectUiCallStartTime(System.nanoTime());
         mCredentialManagerUi.setStatus(CredentialManagerUi.UiStatus.USER_INTERACTION);
+        cancelExistingPendingIntent();
         try {
-            mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent(
+            mPendingIntent = mCredentialManagerUi.createPendingIntent(
                     RequestInfo.newCreateRequestInfo(
                             mRequestId, mClientRequest,
                             mClientAppInfo.getPackageName(),
                             PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
                                     Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)),
-                    providerDataList));
+                    providerDataList);
+            mClientCallback.onPendingIntent(mPendingIntent);
         } catch (RemoteException e) {
             mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false);
             mCredentialManagerUi.setStatus(CredentialManagerUi.UiStatus.TERMINATED);
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index 8750906..0dee7a4 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -33,7 +33,6 @@
 import android.os.Looper;
 import android.os.ResultReceiver;
 import android.service.credentials.CredentialProviderInfoFactory;
-import android.util.Log;
 import android.util.Slog;
 
 import java.util.ArrayList;
@@ -124,7 +123,6 @@
 
     public CredentialManagerUi(Context context, int userId,
             CredentialManagerUiCallback callbacks, Set<ComponentName> enabledProviders) {
-        Log.i(TAG, "In CredentialManagerUi constructor");
         mContext = context;
         mUserId = userId;
         mCallbacks = callbacks;
@@ -151,8 +149,6 @@
      */
     public PendingIntent createPendingIntent(
             RequestInfo requestInfo, ArrayList<ProviderData> providerDataList) {
-        Log.i(TAG, "In createPendingIntent");
-
         List<CredentialProviderInfo> allProviders =
                 CredentialProviderInfoFactory.getCredentialProviderServices(
                         mContext,
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index aeb4801..3dba4a9 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -89,11 +89,13 @@
     protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
         mRequestSessionMetric.collectUiCallStartTime(System.nanoTime());
         mCredentialManagerUi.setStatus(CredentialManagerUi.UiStatus.USER_INTERACTION);
+        cancelExistingPendingIntent();
         try {
-            mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent(
+            mPendingIntent = mCredentialManagerUi.createPendingIntent(
                     RequestInfo.newGetRequestInfo(
                             mRequestId, mClientRequest, mClientAppInfo.getPackageName()),
-                    providerDataList));
+                    providerDataList);
+            mClientCallback.onPendingIntent(mPendingIntent);
         } catch (RemoteException e) {
             mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false);
             mCredentialManagerUi.setStatus(CredentialManagerUi.UiStatus.TERMINATED);
@@ -161,7 +163,8 @@
     @Override
     public void onProviderStatusChanged(ProviderSession.Status status,
             ComponentName componentName, ProviderSession.CredentialsSource source) {
-        Slog.d(TAG, "in onStatusChanged with status: " + status + ", and source: " + source);
+        Slog.d(TAG, "in onStatusChanged for: " + componentName + ", with status: "
+                + status + ", and source: " + source);
 
         // Auth entry was selected, and it did not have any underlying credentials
         if (status == ProviderSession.Status.NO_CREDENTIALS_FROM_AUTH_ENTRY) {
diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
index 0c3d3f4..9ec0ecd 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
@@ -26,7 +26,6 @@
 import android.os.ICancellationSignal;
 import android.service.credentials.CallingAppInfo;
 import android.service.credentials.ClearCredentialStateRequest;
-import android.util.Log;
 import android.util.Slog;
 
 /**
@@ -81,7 +80,7 @@
 
     @Override
     public void onProviderResponseSuccess(@Nullable Void response) {
-        Log.i(TAG, "in onProviderResponseSuccess");
+        Slog.d(TAG, "Remote provider responded with a valid response: " + mComponentName);
         mProviderResponseSet = true;
         updateStatusAndInvokeCallback(Status.COMPLETE,
                 /*source=*/ CredentialsSource.REMOTE_PROVIDER);
@@ -105,7 +104,7 @@
             updateStatusAndInvokeCallback(Status.SERVICE_DEAD,
                     /*source=*/ CredentialsSource.REMOTE_PROVIDER);
         } else {
-            Slog.i(TAG, "Component names different in onProviderServiceDied - "
+            Slog.w(TAG, "Component names different in onProviderServiceDied - "
                     + "this should not happen");
         }
     }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 8b9255a..09433db 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -37,7 +37,6 @@
 import android.service.credentials.CreateEntry;
 import android.service.credentials.CredentialProviderService;
 import android.service.credentials.RemoteEntry;
-import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
 
@@ -93,7 +92,8 @@
                     createRequestSession.mHybridService
             );
         }
-        Log.i(TAG, "Unable to create provider session");
+        Slog.d(TAG, "Unable to create provider session for: "
+                + providerInfo.getComponentName());
         return null;
     }
 
@@ -122,7 +122,6 @@
             return new CreateCredentialRequest(callingAppInfo, capability,
                     clientRequest.getCredentialData());
         }
-        Log.i(TAG, "Unable to create provider request - capabilities do not match");
         return null;
     }
 
@@ -146,7 +145,7 @@
     @Override
     public void onProviderResponseSuccess(
             @Nullable BeginCreateCredentialResponse response) {
-        Log.i(TAG, "in onProviderResponseSuccess");
+        Slog.d(TAG, "Remote provider responded with a valid response: " + mComponentName);
         onSetInitialRemoteResponse(response);
     }
 
@@ -169,7 +168,7 @@
             updateStatusAndInvokeCallback(Status.SERVICE_DEAD,
                     /*source=*/ CredentialsSource.REMOTE_PROVIDER);
         } else {
-            Slog.i(TAG, "Component names different in onProviderServiceDied - "
+            Slog.w(TAG, "Component names different in onProviderServiceDied - "
                     + "this should not happen");
         }
     }
@@ -180,7 +179,6 @@
     }
 
     private void onSetInitialRemoteResponse(BeginCreateCredentialResponse response) {
-        Log.i(TAG, "onSetInitialRemoteResponse with save entries");
         mProviderResponse = response;
         mProviderResponseDataHandler.addResponseContent(response.getCreateEntries(),
                 response.getRemoteCreateEntry());
@@ -199,14 +197,12 @@
     @Nullable
     protected CreateCredentialProviderData prepareUiData()
             throws IllegalArgumentException {
-        Log.i(TAG, "In prepareUiData");
         if (!ProviderSession.isUiInvokingStatus(getStatus())) {
-            Log.i(TAG, "In prepareUiData not in uiInvokingStatus");
+            Slog.d(TAG, "No data for UI from: " + mComponentName.flattenToString());
             return null;
         }
 
         if (mProviderResponse != null && !mProviderResponseDataHandler.isEmptyResponse()) {
-            Log.i(TAG, "In prepareUiData save entries not null");
             return mProviderResponseDataHandler.toCreateCredentialProviderData();
         }
         return null;
@@ -218,7 +214,7 @@
         switch (entryType) {
             case SAVE_ENTRY_KEY:
                 if (mProviderResponseDataHandler.getCreateEntry(entryKey) == null) {
-                    Log.i(TAG, "Unexpected save entry key");
+                    Slog.w(TAG, "Unexpected save entry key");
                     invokeCallbackOnInternalInvalidState();
                     return;
                 }
@@ -226,14 +222,14 @@
                 break;
             case REMOTE_ENTRY_KEY:
                 if (mProviderResponseDataHandler.getRemoteEntry(entryKey) == null) {
-                    Log.i(TAG, "Unexpected remote entry key");
+                    Slog.w(TAG, "Unexpected remote entry key");
                     invokeCallbackOnInternalInvalidState();
                     return;
                 }
                 onRemoteEntrySelected(providerPendingIntentResponse);
                 break;
             default:
-                Log.i(TAG, "Unsupported entry type selected");
+                Slog.w(TAG, "Unsupported entry type selected");
                 invokeCallbackOnInternalInvalidState();
         }
     }
@@ -268,7 +264,7 @@
         if (credentialResponse != null) {
             mCallbacks.onFinalResponseReceived(mComponentName, credentialResponse);
         } else {
-            Log.i(TAG, "onSaveEntrySelected - no response or error found in pending "
+            Slog.w(TAG, "onSaveEntrySelected - no response or error found in pending "
                     + "intent response");
             invokeCallbackOnInternalInvalidState();
         }
@@ -284,14 +280,14 @@
     private CreateCredentialException maybeGetPendingIntentException(
             ProviderPendingIntentResponse pendingIntentResponse) {
         if (pendingIntentResponse == null) {
-            Log.i(TAG, "pendingIntentResponse is null");
+            Slog.w(TAG, "pendingIntentResponse is null");
             return new CreateCredentialException(CreateCredentialException.TYPE_NO_CREATE_OPTIONS);
         }
         if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
             CreateCredentialException exception = PendingIntentResultHandler
                     .extractCreateCredentialException(pendingIntentResponse.getResultData());
             if (exception != null) {
-                Log.i(TAG, "Pending intent contains provider exception");
+                Slog.d(TAG, "Pending intent contains provider exception");
                 return exception;
             }
         } else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
@@ -343,7 +339,7 @@
 
         public void setRemoteEntry(@Nullable RemoteEntry remoteEntry) {
             if (!enforceRemoteEntryRestrictions(mExpectedRemoteEntryProviderService)) {
-                Log.i(TAG, "Remote entry being dropped as it does not meet the restriction"
+                Slog.w(TAG, "Remote entry being dropped as it does not meet the restriction"
                         + "checks.");
                 return;
             }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 8d3d064..0c2b563 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -115,7 +115,8 @@
                     getRequestSession.mHybridService
             );
         }
-        Log.i(TAG, "Unable to create provider session");
+        Slog.d(TAG, "Unable to create provider session for: "
+                + providerInfo.getComponentName());
         return null;
     }
 
@@ -146,17 +147,15 @@
             android.credentials.GetCredentialRequest clientRequest,
             CredentialProviderInfo info
     ) {
+        Slog.d(TAG, "Filtering request options for: " + info.getComponentName());
         List<CredentialOption> filteredOptions = new ArrayList<>();
         for (CredentialOption option : clientRequest.getCredentialOptions()) {
             if (providerCapabilities.contains(option.getType())
                     && isProviderAllowed(option, info.getComponentName())
                     && checkSystemProviderRequirement(option, info.isSystemProvider())) {
-                Log.i(TAG, "In createProviderRequest - capability found : "
-                        + option.getType());
+                Slog.d(TAG, "Option of type: " + option.getType() + " meets all filtering"
+                        + "conditions");
                 filteredOptions.add(option);
-            } else {
-                Log.i(TAG, "In createProviderRequest - capability not "
-                        + "found, or provider not allowed : " + option.getType());
             }
         }
         if (!filteredOptions.isEmpty()) {
@@ -165,15 +164,14 @@
                     .setCredentialOptions(
                             filteredOptions).build();
         }
-        Log.i(TAG, "In createProviderRequest - returning null");
+        Slog.d(TAG, "No options filtered");
         return null;
     }
 
     private static boolean isProviderAllowed(CredentialOption option, ComponentName componentName) {
         if (!option.getAllowedProviders().isEmpty() && !option.getAllowedProviders().contains(
                 componentName)) {
-            Log.d(TAG, "Provider allow list specified but does not contain this provider: "
-                    + componentName.flattenToString());
+            Slog.d(TAG, "Provider allow list specified but does not contain this provider");
             return false;
         }
         return true;
@@ -182,7 +180,7 @@
     private static boolean checkSystemProviderRequirement(CredentialOption option,
             boolean isSystemProvider) {
         if (option.isSystemProviderRequired() && !isSystemProvider) {
-            Log.d(TAG, "System provider required, but this service is not a system provider");
+            Slog.d(TAG, "System provider required, but this service is not a system provider");
             return false;
         }
         return true;
@@ -210,6 +208,7 @@
     /** Called when the provider response has been updated by an external source. */
     @Override // Callback from the remote provider
     public void onProviderResponseSuccess(@Nullable BeginGetCredentialResponse response) {
+        Slog.d(TAG, "Remote provider responded with a valid response: " + mComponentName);
         onSetInitialRemoteResponse(response);
     }
 
@@ -231,7 +230,7 @@
             updateStatusAndInvokeCallback(Status.SERVICE_DEAD,
                     /*source=*/ CredentialsSource.REMOTE_PROVIDER);
         } else {
-            Slog.i(TAG, "Component names different in onProviderServiceDied - "
+            Slog.w(TAG, "Component names different in onProviderServiceDied - "
                     + "this should not happen");
         }
     }
@@ -244,13 +243,14 @@
     @Override // Selection call from the request provider
     protected void onUiEntrySelected(String entryType, String entryKey,
             ProviderPendingIntentResponse providerPendingIntentResponse) {
-        Log.i(TAG, "onUiEntrySelected with entryKey: " + entryKey);
+        Slog.d(TAG, "onUiEntrySelected with entryType: " + entryType + ", and entryKey: "
+                + entryKey);
         switch (entryType) {
             case CREDENTIAL_ENTRY_KEY:
                 CredentialEntry credentialEntry = mProviderResponseDataHandler
                         .getCredentialEntry(entryKey);
                 if (credentialEntry == null) {
-                    Log.i(TAG, "Unexpected credential entry key");
+                    Slog.w(TAG, "Unexpected credential entry key");
                     invokeCallbackOnInternalInvalidState();
                     return;
                 }
@@ -259,7 +259,7 @@
             case ACTION_ENTRY_KEY:
                 Action actionEntry = mProviderResponseDataHandler.getActionEntry(entryKey);
                 if (actionEntry == null) {
-                    Log.i(TAG, "Unexpected action entry key");
+                    Slog.w(TAG, "Unexpected action entry key");
                     invokeCallbackOnInternalInvalidState();
                     return;
                 }
@@ -269,21 +269,21 @@
                 Action authenticationEntry = mProviderResponseDataHandler
                         .getAuthenticationAction(entryKey);
                 if (authenticationEntry == null) {
-                    Log.i(TAG, "Unexpected authenticationEntry key");
+                    Slog.w(TAG, "Unexpected authenticationEntry key");
                     invokeCallbackOnInternalInvalidState();
                     return;
                 }
                 boolean additionalContentReceived =
                         onAuthenticationEntrySelected(providerPendingIntentResponse);
                 if (additionalContentReceived) {
-                    Log.i(TAG, "Additional content received - removing authentication entry");
+                    Slog.d(TAG, "Additional content received - removing authentication entry");
                     mProviderResponseDataHandler.removeAuthenticationAction(entryKey);
                     if (!mProviderResponseDataHandler.isEmptyResponse()) {
                         updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED,
                                 /*source=*/ CredentialsSource.AUTH_ENTRY);
                     }
                 } else {
-                    Log.i(TAG, "Additional content not received");
+                    Slog.d(TAG, "Additional content not received from authentication entry");
                     mProviderResponseDataHandler
                             .updateAuthEntryWithNoCredentialsReceived(entryKey);
                     updateStatusAndInvokeCallback(Status.NO_CREDENTIALS_FROM_AUTH_ENTRY,
@@ -294,12 +294,12 @@
                 if (mProviderResponseDataHandler.getRemoteEntry(entryKey) != null) {
                     onRemoteEntrySelected(providerPendingIntentResponse);
                 } else {
-                    Log.i(TAG, "Unexpected remote entry key");
+                    Slog.d(TAG, "Unexpected remote entry key");
                     invokeCallbackOnInternalInvalidState();
                 }
                 break;
             default:
-                Log.i(TAG, "Unsupported entry type selected");
+                Slog.w(TAG, "Unsupported entry type selected");
                 invokeCallbackOnInternalInvalidState();
         }
     }
@@ -320,26 +320,24 @@
     @Override // Call from request session to data to be shown on the UI
     @Nullable
     protected GetCredentialProviderData prepareUiData() throws IllegalArgumentException {
-        Log.i(TAG, "In prepareUiData");
         if (!ProviderSession.isUiInvokingStatus(getStatus())) {
-            Log.i(TAG, "In prepareUiData - provider does not want to show UI: "
-                    + mComponentName.flattenToString());
+            Slog.d(TAG, "No data for UI from: " + mComponentName.flattenToString());
             return null;
         }
         if (mProviderResponse != null && !mProviderResponseDataHandler.isEmptyResponse()) {
             return mProviderResponseDataHandler.toGetCredentialProviderData();
         }
-        Log.i(TAG, "In prepareUiData response null");
+        Slog.d(TAG, "In prepareUiData response null");
         return null;
     }
 
-    private Intent setUpFillInIntent(@NonNull String id) {
+    private Intent setUpFillInIntentWithFinalRequest(@NonNull String id) {
         // TODO: Determine if we should skip this entry if entry id is not set, or is set
         // but does not resolve to a valid option. For now, not skipping it because
         // it may be possible that the provider adds their own extras and expects to receive
         // those and complete the flow.
         if (mBeginGetOptionToCredentialOptionMap.get(id) == null) {
-            Log.i(TAG, "Id from Credential Entry does not resolve to a valid option");
+            Slog.w(TAG, "Id from Credential Entry does not resolve to a valid option");
             return new Intent();
         }
         return new Intent().putExtra(CredentialProviderService.EXTRA_GET_CREDENTIAL_REQUEST,
@@ -382,7 +380,8 @@
                     getCredentialResponse);
             return;
         }
-        Log.i(TAG, "Pending intent response contains no credential, or error");
+        Slog.d(TAG, "Pending intent response contains no credential, or error "
+                + "for a credential entry");
         invokeCallbackOnInternalInvalidState();
     }
 
@@ -390,14 +389,12 @@
     private GetCredentialException maybeGetPendingIntentException(
             ProviderPendingIntentResponse pendingIntentResponse) {
         if (pendingIntentResponse == null) {
-            Log.i(TAG, "pendingIntentResponse is null");
             return null;
         }
         if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
             GetCredentialException exception = PendingIntentResultHandler
                     .extractGetCredentialException(pendingIntentResponse.getResultData());
             if (exception != null) {
-                Log.i(TAG, "Pending intent contains provider exception");
                 return exception;
             }
         } else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
@@ -463,7 +460,7 @@
     /** Returns true if either an exception or a response is found. */
     private void onActionEntrySelected(ProviderPendingIntentResponse
             providerPendingIntentResponse) {
-        Log.i(TAG, "onActionEntrySelected");
+        Slog.d(TAG, "onActionEntrySelected");
         onCredentialEntrySelected(providerPendingIntentResponse);
     }
 
@@ -559,7 +556,8 @@
             String id = generateUniqueId();
             Entry entry = new Entry(CREDENTIAL_ENTRY_KEY,
                     id, credentialEntry.getSlice(),
-                    setUpFillInIntent(credentialEntry.getBeginGetCredentialOptionId()));
+                    setUpFillInIntentWithFinalRequest(credentialEntry
+                            .getBeginGetCredentialOptionId()));
             mUiCredentialEntries.put(id, new Pair<>(credentialEntry, entry));
             mCredentialEntryTypes.add(credentialEntry.getType());
         }
@@ -574,9 +572,7 @@
 
         public void addAuthenticationAction(Action authenticationAction,
                 @AuthenticationEntry.Status int status) {
-            Log.i(TAG, "In addAuthenticationAction");
             String id = generateUniqueId();
-            Log.i(TAG, "In addAuthenticationAction, id : " + id);
             AuthenticationEntry entry = new AuthenticationEntry(
                     AUTHENTICATION_ACTION_ENTRY_KEY,
                     id, authenticationAction.getSlice(),
@@ -591,7 +587,7 @@
 
         public void setRemoteEntry(@Nullable RemoteEntry remoteEntry) {
             if (!enforceRemoteEntryRestrictions(mExpectedRemoteEntryProviderService)) {
-                Log.i(TAG, "Remote entry being dropped as it does not meet the restriction"
+                Slog.w(TAG, "Remote entry being dropped as it does not meet the restriction"
                         + " checks.");
                 return;
             }
@@ -715,7 +711,6 @@
                             == AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT)
                     .findFirst();
             if (previousMostRecentAuthEntry.isEmpty()) {
-                Log.i(TAG, "In updatePreviousMostRecentAuthEntry - previous entry not found");
                 return;
             }
             String id = previousMostRecentAuthEntry.get().getKey();
diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
index 24292ef2..c10f564 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
@@ -33,7 +33,7 @@
 import android.service.credentials.CallingAppInfo;
 import android.service.credentials.CredentialEntry;
 import android.service.credentials.CredentialProviderService;
-import android.telecom.Log;
+import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -145,14 +145,12 @@
 
     private List<Entry> prepareUiCredentialEntries(
             @NonNull List<CredentialEntry> credentialEntries) {
-        Log.i(TAG, "in prepareUiProviderDataWithCredentials");
         List<Entry> credentialUiEntries = new ArrayList<>();
 
         // Populate the credential entries
         for (CredentialEntry credentialEntry : credentialEntries) {
             String entryId = generateUniqueId();
             mUiCredentialEntries.put(entryId, credentialEntry);
-            Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
             credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
                     credentialEntry.getSlice(),
                     setUpFillInIntent()));
@@ -172,15 +170,13 @@
 
     @Override
     protected ProviderData prepareUiData() {
-        Log.i(TAG, "In prepareUiData");
         if (!ProviderSession.isUiInvokingStatus(getStatus())) {
-            Log.i(TAG, "In prepareUiData - provider does not want to show UI: "
-                    + mComponentName.flattenToString());
+            Slog.d(TAG, "No date for UI coming from: " + mComponentName.flattenToString());
             return null;
         }
         if (mProviderResponse == null) {
-            Log.i(TAG, "In prepareUiData response null");
-            throw new IllegalStateException("Response must be in completion mode");
+            Slog.w(TAG, "In prepareUiData but response is null. This is strange.");
+            return null;
         }
         return new GetCredentialProviderData.Builder(
                 mComponentName.flattenToString()).setActionChips(null)
@@ -200,13 +196,13 @@
             case CREDENTIAL_ENTRY_KEY:
                 CredentialEntry credentialEntry = mUiCredentialEntries.get(entryKey);
                 if (credentialEntry == null) {
-                    Log.i(TAG, "Unexpected credential entry key");
+                    Slog.w(TAG, "Unexpected credential entry key");
                     return;
                 }
                 onCredentialEntrySelected(credentialEntry, providerPendingIntentResponse);
                 break;
             default:
-                Log.i(TAG, "Unsupported entry type selected");
+                Slog.w(TAG, "Unsupported entry type selected");
         }
     }
 
@@ -233,10 +229,8 @@
                 }
                 return;
             }
-
-            Log.i(TAG, "Pending intent response contains no credential, or error");
         }
-        Log.i(TAG, "CredentialEntry does not have a credential or a pending intent result");
+        Slog.w(TAG, "CredentialEntry does not have a credential or a pending intent result");
     }
 
     @Override
@@ -279,14 +273,13 @@
     protected GetCredentialException maybeGetPendingIntentException(
             ProviderPendingIntentResponse pendingIntentResponse) {
         if (pendingIntentResponse == null) {
-            android.util.Log.i(TAG, "pendingIntentResponse is null");
             return null;
         }
         if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
             GetCredentialException exception = PendingIntentResultHandler
                     .extractGetCredentialException(pendingIntentResponse.getResultData());
             if (exception != null) {
-                android.util.Log.i(TAG, "Pending intent contains provider exception");
+                Slog.d(TAG, "Pending intent contains provider exception");
                 return exception;
             }
         } else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index d165756..d02a8c1 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -29,7 +29,6 @@
 import android.credentials.ui.ProviderPendingIntentResponse;
 import android.os.ICancellationSignal;
 import android.os.RemoteException;
-import android.util.Log;
 import android.util.Slog;
 
 import com.android.server.credentials.metrics.ProviderSessionMetric;
@@ -253,7 +252,7 @@
             @Nullable ComponentName expectedRemoteEntryProviderService) {
         // Check if the service is the one set by the OEM. If not silently reject this entry
         if (!mComponentName.equals(expectedRemoteEntryProviderService)) {
-            Log.i(TAG, "Remote entry being dropped as it is not from the service "
+            Slog.w(TAG, "Remote entry being dropped as it is not from the service "
                     + "configured by the OEM.");
             return false;
         }
@@ -270,15 +269,12 @@
                 return true;
             }
         } catch (SecurityException e) {
-            Log.i(TAG, "Error getting info for "
-                    + mComponentName.flattenToString() + ": " + e.getMessage());
+            Slog.e(TAG, "Error getting info for " + mComponentName.flattenToString(), e);
             return false;
         } catch (PackageManager.NameNotFoundException e) {
-            Log.i(TAG, "Error getting info for "
-                    + mComponentName.flattenToString() + ": " + e.getMessage());
+            Slog.i(TAG, "Error getting info for " + mComponentName.flattenToString(), e);
             return false;
         }
-        Log.i(TAG, "In enforceRemoteEntryRestrictions - remote entry checks fail");
         return false;
     }
 
diff --git a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
index c1e9bc6..0ad73c9 100644
--- a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
+++ b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
@@ -40,7 +40,6 @@
 import android.service.credentials.IClearCredentialStateCallback;
 import android.service.credentials.ICredentialProviderService;
 import android.text.format.DateUtils;
-import android.util.Log;
 import android.util.Slog;
 
 import com.android.internal.infra.ServiceConnector;
@@ -122,7 +121,6 @@
      */
     public void onBeginGetCredential(@NonNull BeginGetCredentialRequest request,
             ProviderCallbacks<BeginGetCredentialResponse> callback) {
-        Log.i(TAG, "In onGetCredentials in RemoteCredentialService");
         AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
         AtomicReference<CompletableFuture<BeginGetCredentialResponse>> futureRef =
                 new AtomicReference<>();
@@ -142,7 +140,6 @@
 
                             @Override
                             public void onFailure(String errorType, CharSequence message) {
-                                Log.i(TAG, "In onFailure in RemoteCredentialService");
                                 String errorMsg = message == null ? "" : String.valueOf(
                                         message);
                                 getCredentials.completeExceptionally(
@@ -182,7 +179,6 @@
      */
     public void onBeginCreateCredential(@NonNull BeginCreateCredentialRequest request,
             ProviderCallbacks<BeginCreateCredentialResponse> callback) {
-        Log.i(TAG, "In onCreateCredential in RemoteCredentialService");
         AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
         AtomicReference<CompletableFuture<BeginCreateCredentialResponse>> futureRef =
                 new AtomicReference<>();
@@ -197,14 +193,11 @@
                                 request, new IBeginCreateCredentialCallback.Stub() {
                                     @Override
                                     public void onSuccess(BeginCreateCredentialResponse response) {
-                                        Log.i(TAG, "In onSuccess onBeginCreateCredential "
-                                                + "in RemoteCredentialService");
                                         createCredentialFuture.complete(response);
                                     }
 
                                     @Override
                                     public void onFailure(String errorType, CharSequence message) {
-                                        Log.i(TAG, "In onFailure in RemoteCredentialService");
                                         String errorMsg = message == null ? "" : String.valueOf(
                                                 message);
                                         createCredentialFuture.completeExceptionally(
@@ -244,7 +237,6 @@
      */
     public void onClearCredentialState(@NonNull ClearCredentialStateRequest request,
             ProviderCallbacks<Void> callback) {
-        Log.i(TAG, "In onClearCredentialState in RemoteCredentialService");
         AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
         AtomicReference<CompletableFuture<Void>> futureRef = new AtomicReference<>();
 
@@ -258,14 +250,11 @@
                                 request, new IClearCredentialStateCallback.Stub() {
                                     @Override
                                     public void onSuccess() {
-                                        Log.i(TAG, "In onSuccess onClearCredentialState "
-                                                + "in RemoteCredentialService");
                                         clearCredentialFuture.complete(null);
                                     }
 
                                     @Override
                                     public void onFailure(String errorType, CharSequence message) {
-                                        Log.i(TAG, "In onFailure in RemoteCredentialService");
                                         String errorMsg = message == null ? "" :
                                                 String.valueOf(message);
                                         clearCredentialFuture.completeExceptionally(
@@ -300,35 +289,29 @@
             AtomicReference<ICancellationSignal> cancellationSink,
             ProviderCallbacks<T> callback) {
         if (error == null) {
-            Log.i(TAG, "In RemoteCredentialService execute error is null");
             callback.onProviderResponseSuccess(result);
         } else {
             if (error instanceof TimeoutException) {
-                Log.i(TAG, "In RemoteCredentialService execute error is timeout");
+                Slog.d(TAG, "Remote provider response timed tuo for: " + mComponentName);
                 dispatchCancellationSignal(cancellationSink.get());
                 callback.onProviderResponseFailure(
                         CredentialProviderErrors.ERROR_TIMEOUT,
                         null);
             } else if (error instanceof CancellationException) {
-                Log.i(TAG, "In RemoteCredentialService execute error is cancellation");
+                Slog.d(TAG, "Cancellation exception for remote provider: " + mComponentName);
                 dispatchCancellationSignal(cancellationSink.get());
                 callback.onProviderResponseFailure(
                         CredentialProviderErrors.ERROR_TASK_CANCELED,
                         null);
             } else if (error instanceof GetCredentialException) {
-                Log.i(TAG, "In RemoteCredentialService execute error is provider get"
-                        + "error");
                 callback.onProviderResponseFailure(
                         CredentialProviderErrors.ERROR_PROVIDER_FAILURE,
                         (GetCredentialException) error);
             } else if (error instanceof CreateCredentialException) {
-                Log.i(TAG, "In RemoteCredentialService execute error is provider create "
-                        + "error");
                 callback.onProviderResponseFailure(
                         CredentialProviderErrors.ERROR_PROVIDER_FAILURE,
                         (CreateCredentialException) error);
             } else {
-                Log.i(TAG, "In RemoteCredentialService execute error is unknown");
                 callback.onProviderResponseFailure(
                         CredentialProviderErrors.ERROR_UNKNOWN,
                         (Exception) error);
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index 8fd0269..15a30e4 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -90,6 +91,8 @@
 
     private final Set<ComponentName> mEnabledProviders;
 
+    protected PendingIntent mPendingIntent;
+
     @NonNull
     protected RequestSessionStatus mRequestSessionStatus =
             RequestSessionStatus.IN_PROGRESS;
@@ -202,11 +205,23 @@
         if (propagateCancellation) {
             mProviders.values().forEach(ProviderSession::cancelProviderRemoteSession);
         }
+        cancelExistingPendingIntent();
         mRequestSessionStatus = RequestSessionStatus.COMPLETE;
         mProviders.clear();
         clearRequestSessionLocked();
     }
 
+    void cancelExistingPendingIntent() {
+        if (mPendingIntent != null) {
+            try {
+                mPendingIntent.cancel();
+                mPendingIntent = null;
+            } catch (Exception e) {
+                Slog.e(TAG, "Unable to cancel existing pending intent", e);
+            }
+        }
+    }
+
     private void clearRequestSessionLocked() {
         synchronized (mLock) {
             mSessionCallback.onFinishRequestSession(mUserId, mRequestId);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index f111a95..3d5686d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -27,6 +27,7 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.BroadcastOptions;
 import android.app.admin.DevicePolicyIdentifiers;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyState;
@@ -353,6 +354,7 @@
                     policyDefinition,
                     userId);
         }
+        sendDevicePolicyChangedToSystem(userId);
     }
 
     /**
@@ -478,6 +480,8 @@
                 enforcingAdmin,
                 policyDefinition,
                 UserHandle.USER_ALL);
+
+        sendDevicePolicyChangedToSystem(UserHandle.USER_ALL);
     }
 
     /**
@@ -699,7 +703,7 @@
 
         if (policyDefinition.isGlobalOnlyPolicy()) {
             throw new IllegalArgumentException(policyDefinition.getPolicyKey() + " is a global only"
-                    + "policy.");
+                    + " policy.");
         }
 
         if (!mLocalPolicies.contains(userId)) {
@@ -724,7 +728,7 @@
     private <V> PolicyState<V> getGlobalPolicyStateLocked(PolicyDefinition<V> policyDefinition) {
         if (policyDefinition.isLocalOnlyPolicy()) {
             throw new IllegalArgumentException(policyDefinition.getPolicyKey() + " is a local only"
-                    + "policy.");
+                    + " policy.");
         }
 
         if (!mGlobalPolicies.containsKey(policyDefinition.getPolicyKey())) {
@@ -761,6 +765,20 @@
                 policyValue == null ? null : policyValue.getValue(), mContext, userId);
     }
 
+    private void sendDevicePolicyChangedToSystem(int userId) {
+        Intent intent = new Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
+        intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        Bundle options = new BroadcastOptions()
+                .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+                .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
+                .toBundle();
+        Binder.withCleanCallingIdentity(() -> mContext.sendBroadcastAsUser(
+                intent,
+                new UserHandle(userId),
+                /* receiverPermissions= */ null,
+                options));
+    }
+
     private <V> void sendPolicyResultToAdmin(
             EnforcingAdmin admin, PolicyDefinition<V> policyDefinition, int result, int userId) {
         Intent intent = new Intent(PolicyUpdateReceiver.ACTION_DEVICE_POLICY_SET_RESULT);
@@ -892,7 +910,7 @@
             mDeviceAdminServiceController.stopServicesForUser(
                     userId, actionForLog);
         } else {
-            for (EnforcingAdmin admin : getEnforcingAdminsForUser(userId)) {
+            for (EnforcingAdmin admin : getEnforcingAdminsOnUser(userId)) {
                 // DPCs are handled separately in DPMS, no need to reestablish the connection here.
                 if (admin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) {
                     continue;
@@ -903,6 +921,51 @@
         }
     }
 
+    /**
+     * Handles internal state related to a user getting started.
+     */
+    void handleStartUser(int userId) {
+        updateDeviceAdminsServicesForUser(
+                userId, /* enable= */ true, /* actionForLog= */ "start-user");
+    }
+
+    /**
+     * Handles internal state related to a user getting started.
+     */
+    void handleUnlockUser(int userId) {
+        updateDeviceAdminsServicesForUser(
+                userId, /* enable= */ true, /* actionForLog= */ "unlock-user");
+    }
+
+    /**
+     * Handles internal state related to a user getting stopped.
+     */
+    void handleStopUser(int userId) {
+        updateDeviceAdminsServicesForUser(
+                userId, /* enable= */ false, /* actionForLog= */ "stop-user");
+    }
+
+    /**
+     * Handles internal state related to packages getting updated.
+     */
+    void handlePackageChanged(@Nullable String updatedPackage, int userId) {
+        if (updatedPackage == null) {
+            return;
+        }
+        updateDeviceAdminServiceOnPackageChanged(updatedPackage, userId);
+    }
+
+    /**
+     * Handles internal state related to a user getting removed.
+     */
+    void handleUserRemoved(int userId) {
+        removeLocalPoliciesForUser(userId);
+        removePoliciesForAdminsOnUser(userId);
+    }
+
+    /**
+     * Handles internal state related to a user getting created.
+     */
     void handleUserCreated(UserInfo user) {
         enforcePoliciesOnInheritableProfilesIfApplicable(user);
     }
@@ -945,40 +1008,6 @@
     }
 
     /**
-     * Handles internal state related to a user getting started.
-     */
-    void handleStartUser(int userId) {
-        updateDeviceAdminsServicesForUser(
-                userId, /* enable= */ true, /* actionForLog= */ "start-user");
-    }
-
-    /**
-     * Handles internal state related to a user getting started.
-     */
-    void handleUnlockUser(int userId) {
-        updateDeviceAdminsServicesForUser(
-                userId, /* enable= */ true, /* actionForLog= */ "unlock-user");
-    }
-
-    /**
-     * Handles internal state related to a user getting stopped.
-     */
-    void handleStopUser(int userId) {
-        updateDeviceAdminsServicesForUser(
-                userId, /* enable= */ false, /* actionForLog= */ "stop-user");
-    }
-
-    /**
-     * Handles internal state related to packages getting updated.
-     */
-    void handlePackageChanged(@Nullable String updatedPackage, int userId) {
-        if (updatedPackage == null) {
-            return;
-        }
-        updateDeviceAdminServiceOnPackageChanged(updatedPackage, userId);
-    }
-
-    /**
      * Returns all current enforced policies set on the device, and the individual values set by
      * each admin. Global policies are returned under {@link UserHandle#ALL}.
      */
@@ -1006,6 +1035,63 @@
         return new DevicePolicyState(policies);
     }
 
+
+    /**
+     * Removes all local and global policies set by that admin.
+     */
+    void removePoliciesForAdmin(EnforcingAdmin admin) {
+        Set<PolicyKey> globalPolicies = new HashSet<>(mGlobalPolicies.keySet());
+        for (PolicyKey policy : globalPolicies) {
+            PolicyState<?> policyState = mGlobalPolicies.get(policy);
+            if (policyState.getPoliciesSetByAdmins().containsKey(admin)) {
+                removeGlobalPolicy(policyState.getPolicyDefinition(), admin);
+            }
+        }
+
+        for (int i = 0; i < mLocalPolicies.size(); i++) {
+            Set<PolicyKey> localPolicies = new HashSet<>(
+                    mLocalPolicies.get(mLocalPolicies.keyAt(i)).keySet());
+            for (PolicyKey policy : localPolicies) {
+                PolicyState<?> policyState = mLocalPolicies.get(
+                        mLocalPolicies.keyAt(i)).get(policy);
+                if (policyState.getPoliciesSetByAdmins().containsKey(admin)) {
+                    removeLocalPolicy(
+                            policyState.getPolicyDefinition(), admin, mLocalPolicies.keyAt(i));
+                }
+            }
+        }
+    }
+
+    /**
+     * Removes all local policies for the provided {@code userId}.
+     */
+    private void removeLocalPoliciesForUser(int userId) {
+        Set<PolicyKey> localPolicies = new HashSet<>(mLocalPolicies.get(userId).keySet());
+        for (PolicyKey policy : localPolicies) {
+            PolicyState<?> policyState = mLocalPolicies.get(userId).get(policy);
+            Set<EnforcingAdmin> admins = new HashSet<>(
+                    policyState.getPoliciesSetByAdmins().keySet());
+            for (EnforcingAdmin admin : admins) {
+                removeLocalPolicy(
+                        policyState.getPolicyDefinition(), admin, userId);
+            }
+        }
+
+        mLocalPolicies.remove(userId);
+    }
+
+    /**
+     * Removes all local and global policies for admins installed in the provided
+     * {@code userId}.
+     */
+    private void removePoliciesForAdminsOnUser(int userId) {
+        Set<EnforcingAdmin> admins = getEnforcingAdminsOnUser(userId);
+
+        for (EnforcingAdmin admin : admins) {
+            removePoliciesForAdmin(admin);
+        }
+    }
+
     /**
      * Reestablishes the service that handles
      * {@link DevicePolicyManager#ACTION_DEVICE_ADMIN_SERVICE} in the enforcing admin if the package
@@ -1013,7 +1099,7 @@
      */
     private void updateDeviceAdminServiceOnPackageChanged(
             @NonNull String updatedPackage, int userId) {
-        for (EnforcingAdmin admin : getEnforcingAdminsForUser(userId)) {
+        for (EnforcingAdmin admin : getEnforcingAdminsOnUser(userId)) {
             // DPCs are handled separately in DPMS, no need to reestablish the connection here.
             if (admin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) {
                 continue;
@@ -1102,7 +1188,7 @@
     }
 
     @NonNull
-    private Set<EnforcingAdmin> getEnforcingAdminsForUser(int userId) {
+    private Set<EnforcingAdmin> getEnforcingAdminsOnUser(int userId) {
         return mEnforcingAdmins.contains(userId)
                 ? mEnforcingAdmins.get(userId) : Collections.emptySet();
     }
@@ -1141,12 +1227,6 @@
         }
     }
 
-    // TODO: we need to listen for user removal and package removal and update out internal policy
-    //  map and enforcing admins for this is be accurate.
-    boolean hasActivePolicies() {
-        return mEnforcingAdmins.size() > 0;
-    }
-
     private <V> boolean checkFor2gFailure(@NonNull PolicyDefinition<V> policyDefinition,
             @NonNull EnforcingAdmin enforcingAdmin) {
         if (!policyDefinition.getPolicyKey().getIdentifier().equals(
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 94e6e73..c2766c2 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.BIND_DEVICE_ADMIN;
 import static android.Manifest.permission.LOCK_DEVICE;
 import static android.Manifest.permission.MANAGE_CA_CERTIFICATES;
+import static android.Manifest.permission.MANAGE_DEFAULT_APPLICATIONS;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL;
@@ -59,6 +60,7 @@
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RESET_PASSWORD;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RUN_IN_BACKGROUND;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SAFE_BOOT;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SCREEN_CAPTURE;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SCREEN_CONTENT;
@@ -441,7 +443,6 @@
 import android.util.DebugUtils;
 import android.util.IndentingPrintWriter;
 import android.util.IntArray;
-import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -855,7 +856,7 @@
 
     private static final String ENABLE_DEVICE_POLICY_ENGINE_FOR_FINANCE_FLAG =
             "enable_device_policy_engine";
-    private static final boolean DEFAULT_ENABLE_DEVICE_POLICY_ENGINE_FOR_FINANCE_FLAG = false;
+    private static final boolean DEFAULT_ENABLE_DEVICE_POLICY_ENGINE_FOR_FINANCE_FLAG = true;
 
     // TODO(b/265683382) remove the flag after rollout.
     private static final String KEEP_PROFILES_RUNNING_FLAG = "enable_keep_profiles_running";
@@ -1177,6 +1178,9 @@
                         // Resume logging if all remaining users are affiliated.
                         maybeResumeDeviceWideLoggingLocked();
                     }
+                    if (isPolicyEngineForFinanceFlagEnabled() || isPermissionCheckFlagEnabled()) {
+                        mDevicePolicyEngine.handleUserRemoved(userHandle);
+                    }
                 }
             } else if (Intent.ACTION_USER_STARTED.equals(action)) {
                 sendDeviceOwnerUserCommand(DeviceAdminReceiver.ACTION_USER_STARTED, userHandle);
@@ -3663,6 +3667,9 @@
         }
         for (Integer userId : deletedUsers) {
             removeUserData(userId);
+            if (isPolicyEngineForFinanceFlagEnabled() || isPermissionCheckFlagEnabled()) {
+                mDevicePolicyEngine.handleUserRemoved(userId);
+            }
         }
     }
 
@@ -4152,6 +4159,11 @@
 
             mInjector.binderWithCleanCallingIdentity(() ->
                     removeActiveAdminLocked(adminReceiver, userHandle));
+            if (isPolicyEngineForFinanceFlagEnabled() || isPermissionCheckFlagEnabled()) {
+                mDevicePolicyEngine.removePoliciesForAdmin(
+                        EnforcingAdmin.createEnterpriseEnforcingAdmin(
+                                adminReceiver, userHandle, admin));
+            }
         }
     }
 
@@ -7767,12 +7779,14 @@
                 // Explicit behaviour
                 if (factoryReset) {
                     // TODO(b/254031494) Replace with new factory reset permission checks
-                    boolean hasPermission = isDeviceOwnerUserId(userId)
-                            || (isOrganizationOwnedDeviceWithManagedProfile()
-                            && calledOnParentInstance);
-                    Preconditions.checkState(hasPermission,
-                            "Admin %s does not have permission to factory reset the device.",
-                            userId);
+                    if (!isPermissionCheckFlagEnabled()) {
+                        boolean hasPermission = isDeviceOwnerUserId(userId)
+                                || (isOrganizationOwnedDeviceWithManagedProfile()
+                                && calledOnParentInstance);
+                        Preconditions.checkCallAuthorization(hasPermission,
+                                "Admin %s does not have permission to factory reset the device.",
+                                userId);
+                    }
                     wipeDevice = true;
                 } else {
                     Preconditions.checkCallAuthorization(!isSystemUser,
@@ -9989,6 +10003,12 @@
         toggleBackupServiceActive(UserHandle.USER_SYSTEM, true);
         pushUserControlDisabledPackagesLocked(userId);
         setGlobalSettingDeviceOwnerType(DEVICE_OWNER_TYPE_DEFAULT);
+
+        if (isPolicyEngineForFinanceFlagEnabled() || isPermissionCheckFlagEnabled()) {
+            mDevicePolicyEngine.removePoliciesForAdmin(
+                    EnforcingAdmin.createEnterpriseEnforcingAdmin(
+                            admin.info.getComponent(), userId, admin));
+        }
     }
 
     private void clearApplicationRestrictions(int userId) {
@@ -10136,6 +10156,12 @@
         toggleBackupServiceActive(userId, true);
         applyProfileRestrictionsIfDeviceOwnerLocked();
         setNetworkLoggingActiveInternal(false);
+
+        if (isPolicyEngineForFinanceFlagEnabled() || isPermissionCheckFlagEnabled()) {
+            mDevicePolicyEngine.removePoliciesForAdmin(
+                    EnforcingAdmin.createEnterpriseEnforcingAdmin(
+                            admin.info.getComponent(), userId, admin));
+        }
     }
 
     @Override
@@ -13131,19 +13157,22 @@
         }
         int userId = caller.getUserId();
 
-        if (!UserRestrictionsUtils.isValidRestriction(key)) {
-            return;
-        }
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_USER_RESTRICTION);
 
         if (isPolicyEngineForFinanceFlagEnabled()) {
-            int affectedUserId = parent ? getProfileParentId(userId) : userId;
-            EnforcingAdmin admin = enforcePermissionForUserRestriction(
-                    who,
-                    key,
-                    caller.getPackageName(),
-                    affectedUserId);
-            if (mInjector.isChangeEnabled(ENABLE_COEXISTENCE_CHANGE, callerPackage, userId)) {
+            if (!isDeviceOwner(caller) && !isProfileOwner(caller)) {
+                if (!mInjector.isChangeEnabled(ENABLE_COEXISTENCE_CHANGE, callerPackage, userId)) {
+                    throw new IllegalStateException("Calling package is not targeting Android U.");
+                }
+                if (!UserRestrictionsUtils.isValidRestriction(key)) {
+                    throw new IllegalArgumentException("Invalid restriction key: " + key);
+                }
+                int affectedUserId = parent ? getProfileParentId(userId) : userId;
+                EnforcingAdmin admin = enforcePermissionForUserRestriction(
+                        who,
+                        key,
+                        caller.getPackageName(),
+                        affectedUserId);
                 PolicyDefinition<Boolean> policyDefinition =
                         PolicyDefinition.getPolicyDefinitionForUserRestriction(key);
                 if (enabledFromThisOwner) {
@@ -13155,7 +13184,8 @@
                         setGlobalUserRestrictionInternal(admin, key, /* enabled= */ false);
                     }
                     if (!policyDefinition.isGlobalOnlyPolicy()) {
-                        setLocalUserRestrictionInternal(admin, key, /* enabled= */ false, userId);
+                        setLocalUserRestrictionInternal(admin, key, /* enabled= */ false,
+                                userId);
 
                         int parentUserId = getProfileParentId(userId);
                         if (parentUserId != userId) {
@@ -13165,49 +13195,21 @@
                     }
                 }
             } else {
+                if (!UserRestrictionsUtils.isValidRestriction(key)) {
+                    return;
+                }
+                Objects.requireNonNull(who, "ComponentName is null");
+                EnforcingAdmin admin = getEnforcingAdminForCaller(who, callerPackage);
+                checkAdminCanSetRestriction(caller, parent, key);
                 setBackwardCompatibleUserRestriction(
                         caller, admin, key, enabledFromThisOwner, parent);
             }
         } else {
+            if (!UserRestrictionsUtils.isValidRestriction(key)) {
+                return;
+            }
             Objects.requireNonNull(who, "ComponentName is null");
-            if (parent) {
-                Preconditions.checkCallAuthorization(
-                        isProfileOwnerOfOrganizationOwnedDevice(caller));
-            } else {
-                Preconditions.checkCallAuthorization(
-                        isDeviceOwner(caller) || isProfileOwner(caller));
-            }
-            synchronized (getLockObject()) {
-                if (isDefaultDeviceOwner(caller)) {
-                    if (!UserRestrictionsUtils.canDeviceOwnerChange(key)) {
-                        throw new SecurityException("Device owner cannot set user restriction "
-                                + key);
-                    }
-                    Preconditions.checkArgument(!parent,
-                            "Cannot use the parent instance in Device Owner mode");
-                } else if (isFinancedDeviceOwner(caller)) {
-                    if (!UserRestrictionsUtils.canFinancedDeviceOwnerChange(key)) {
-                        throw new SecurityException("Cannot set user restriction " + key
-                                + " when managing a financed device");
-                    }
-                    Preconditions.checkArgument(!parent,
-                            "Cannot use the parent instance in Financed Device Owner"
-                                    + " mode");
-                } else {
-                    boolean profileOwnerCanChangeOnItself = !parent
-                            && UserRestrictionsUtils.canProfileOwnerChange(
-                            key, userId == getMainUserId());
-                    boolean orgOwnedProfileOwnerCanChangeGlobally = parent
-                            && isProfileOwnerOfOrganizationOwnedDevice(caller)
-                            && UserRestrictionsUtils.canProfileOwnerOfOrganizationOwnedDeviceChange(
-                            key);
-
-                    if (!profileOwnerCanChangeOnItself && !orgOwnedProfileOwnerCanChangeGlobally) {
-                        throw new SecurityException("Profile owner cannot set user restriction "
-                                + key);
-                    }
-                }
-            }
+            checkAdminCanSetRestriction(caller, parent, key);
             synchronized (getLockObject()) {
                 final ActiveAdmin activeAdmin = getParentOfAdminIfRequired(
                         getProfileOwnerOrDeviceOwnerLocked(userId), parent);
@@ -13224,6 +13226,46 @@
         logUserRestrictionCall(key, enabledFromThisOwner, parent, caller);
     }
 
+    private void checkAdminCanSetRestriction(CallerIdentity caller, boolean parent, String key) {
+        if (parent) {
+            Preconditions.checkCallAuthorization(
+                    isProfileOwnerOfOrganizationOwnedDevice(caller));
+        } else {
+            Preconditions.checkCallAuthorization(
+                    isDeviceOwner(caller) || isProfileOwner(caller));
+        }
+        synchronized (getLockObject()) {
+            if (isDefaultDeviceOwner(caller)) {
+                if (!UserRestrictionsUtils.canDeviceOwnerChange(key)) {
+                    throw new SecurityException("Device owner cannot set user restriction "
+                            + key);
+                }
+                Preconditions.checkArgument(!parent,
+                        "Cannot use the parent instance in Device Owner mode");
+            } else if (isFinancedDeviceOwner(caller)) {
+                if (!UserRestrictionsUtils.canFinancedDeviceOwnerChange(key)) {
+                    throw new SecurityException("Cannot set user restriction " + key
+                            + " when managing a financed device");
+                }
+                Preconditions.checkArgument(!parent,
+                        "Cannot use the parent instance in Financed Device Owner"
+                                + " mode");
+            } else {
+                boolean profileOwnerCanChangeOnItself = !parent
+                        && UserRestrictionsUtils.canProfileOwnerChange(
+                        key, caller.getUserId() == getMainUserId());
+                boolean orgOwnedProfileOwnerCanChangeGlobally = parent
+                        && isProfileOwnerOfOrganizationOwnedDevice(caller)
+                        && UserRestrictionsUtils.canProfileOwnerOfOrganizationOwnedDeviceChange(
+                        key);
+
+                if (!profileOwnerCanChangeOnItself && !orgOwnedProfileOwnerCanChangeGlobally) {
+                    throw new SecurityException("Profile owner cannot set user restriction "
+                            + key);
+                }
+            }
+        }
+    }
     private void setBackwardCompatibleUserRestriction(
             CallerIdentity caller, EnforcingAdmin admin, String key, boolean enabled,
             boolean parent) {
@@ -13245,6 +13287,9 @@
                             ? getProfileParentId(caller.getUserId()) : caller.getUserId();
                     setLocalUserRestrictionInternal(admin, key, enabled, affectedUserId);
                 }
+            } else {
+                throw new IllegalStateException("Non-DO/Non-PO cannot set restriction " + key
+                        + " while targetSdkVersion is less than UPSIDE_DOWN_CAKE");
             }
         }
     }
@@ -13252,33 +13297,34 @@
     @Override
     public void setUserRestrictionGlobally(String callerPackage, String key) {
         final CallerIdentity caller = getCallerIdentity(callerPackage);
-        if (!UserRestrictionsUtils.isValidRestriction(key)) {
-            return;
-        }
 
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_USER_RESTRICTION);
 
         if (!isPolicyEngineForFinanceFlagEnabled()) {
             throw new IllegalStateException("Feature flag is not enabled.");
         }
-
+        if (isDeviceOwner(caller) || isProfileOwner(caller)) {
+            throw new IllegalStateException("Admins are not allowed to call this API.");
+        }
         if (!mInjector.isChangeEnabled(
                 ENABLE_COEXISTENCE_CHANGE, callerPackage, caller.getUserId())) {
             throw new IllegalStateException("Calling package is not targeting Android U.");
         }
+        if (!UserRestrictionsUtils.isValidRestriction(key)) {
+            throw new IllegalArgumentException("Invalid restriction key: " + key);
+        }
 
         EnforcingAdmin admin = enforcePermissionForUserRestriction(
                 /* who= */ null,
                 key,
                 caller.getPackageName(),
-                caller.getUserId()
+                UserHandle.USER_ALL
         );
 
         setGlobalUserRestrictionInternal(admin, key, /* enabled= */ true);
 
         logUserRestrictionCall(key, /* enabled= */ true, /* parent= */ false, caller);
     }
-
     private void setLocalUserRestrictionInternal(
             EnforcingAdmin admin, String key, boolean enabled, int userId) {
         PolicyDefinition<Boolean> policyDefinition =
@@ -13296,7 +13342,6 @@
                     userId);
         }
     }
-
     private void setGlobalUserRestrictionInternal(
             EnforcingAdmin admin, String key, boolean enabled) {
         PolicyDefinition<Boolean> policyDefinition =
@@ -13416,14 +13461,25 @@
             int targetUserId = parent
                     ? getProfileParentId(caller.getUserId()) : caller.getUserId();
             EnforcingAdmin admin = getEnforcingAdminForCaller(who, callerPackage);
-            Bundle restrictions = getUserRestrictionsFromPolicyEngine(admin, targetUserId);
-            // Add global restrictions set by the admin as well if admin is not targeting Android U.
-            if (!mInjector.isChangeEnabled(
-                    ENABLE_COEXISTENCE_CHANGE, callerPackage, caller.getUserId())) {
+            if (isDeviceOwner(caller) || isProfileOwner(caller)) {
+                Objects.requireNonNull(who, "ComponentName is null");
+                Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+                        || isFinancedDeviceOwner(caller)
+                        || isProfileOwner(caller)
+                        || (parent && isProfileOwnerOfOrganizationOwnedDevice(caller)));
+
+                Bundle restrictions = getUserRestrictionsFromPolicyEngine(admin, targetUserId);
+                // Add global restrictions set by the admin as well.
                 restrictions.putAll(
                         getUserRestrictionsFromPolicyEngine(admin, UserHandle.USER_ALL));
+                return restrictions;
+            } else {
+                if (!mInjector.isChangeEnabled(
+                        ENABLE_COEXISTENCE_CHANGE, callerPackage, caller.getUserId())) {
+                    throw new IllegalStateException("Calling package is not targeting Android U.");
+                }
+                return getUserRestrictionsFromPolicyEngine(admin, targetUserId);
             }
-            return restrictions;
         } else {
             Objects.requireNonNull(who, "ComponentName is null");
             Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
@@ -13439,164 +13495,162 @@
     }
 
     // Map of user restriction to permission.
-    private static final HashMap<String, String> USER_RESTRICTION_PERMISSIONS = new HashMap<>();
+    private static final HashMap<String, String[]> USER_RESTRICTION_PERMISSIONS = new HashMap<>();
     {
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.ENSURE_VERIFY_APPS, MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES);
+                UserManager.ALLOW_PARENT_PROFILE_APP_LINKING, new String[]{MANAGE_DEVICE_POLICY_PROFILES});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_WIFI_TETHERING, MANAGE_DEVICE_POLICY_WIFI);
+                UserManager.DISALLOW_ADD_CLONE_PROFILE, new String[]{MANAGE_DEVICE_POLICY_PROFILES});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_WIFI_DIRECT, MANAGE_DEVICE_POLICY_WIFI);
+                UserManager.DISALLOW_ADD_USER, new String[]{MANAGE_DEVICE_POLICY_USERS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_USER_SWITCH, MANAGE_DEVICE_POLICY_USERS);
+                UserManager.DISALLOW_ADD_WIFI_CONFIG, new String[]{MANAGE_DEVICE_POLICY_WIFI});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_USB_FILE_TRANSFER, MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER);
+                UserManager.DISALLOW_ADJUST_VOLUME, new String[]{MANAGE_DEVICE_POLICY_AUDIO_OUTPUT});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_UNMUTE_MICROPHONE, MANAGE_DEVICE_POLICY_MICROPHONE);
+                UserManager.DISALLOW_AIRPLANE_MODE, new String[]{MANAGE_DEVICE_POLICY_AIRPLANE_MODE});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_UNMUTE_DEVICE, MANAGE_DEVICE_POLICY_AUDIO_OUTPUT);
+                UserManager.DISALLOW_AMBIENT_DISPLAY, new String[]{MANAGE_DEVICE_POLICY_DISPLAY});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_UNINSTALL_APPS, MANAGE_DEVICE_POLICY_APPS_CONTROL);
+                UserManager.DISALLOW_APPS_CONTROL, new String[]{MANAGE_DEVICE_POLICY_APPS_CONTROL});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_UNIFIED_PASSWORD, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS);
+                UserManager.DISALLOW_AUTOFILL, new String[]{MANAGE_DEVICE_POLICY_AUTOFILL});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS, MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS);
+                UserManager.DISALLOW_BLUETOOTH, new String[]{MANAGE_DEVICE_POLICY_BLUETOOTH});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_SMS, MANAGE_DEVICE_POLICY_SMS);
+                UserManager.DISALLOW_BLUETOOTH_SHARING, new String[]{MANAGE_DEVICE_POLICY_BLUETOOTH});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, MANAGE_DEVICE_POLICY_WIFI);
+                UserManager.DISALLOW_CAMERA, new String[]{MANAGE_DEVICE_POLICY_CAMERA});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_SHARE_LOCATION, MANAGE_DEVICE_POLICY_LOCATION);
+                UserManager.DISALLOW_CAMERA_TOGGLE, new String[]{MANAGE_DEVICE_POLICY_CAMERA_TOGGLE});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE,
-                MANAGE_DEVICE_POLICY_PROFILE_INTERACTION);
+                UserManager.DISALLOW_CELLULAR_2G, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_SET_WALLPAPER, MANAGE_DEVICE_POLICY_WALLPAPER);
+                UserManager.DISALLOW_CHANGE_WIFI_STATE, new String[]{MANAGE_DEVICE_POLICY_WIFI});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_SET_USER_ICON, MANAGE_DEVICE_POLICY_USERS);
+                UserManager.DISALLOW_CONFIG_BLUETOOTH, new String[]{MANAGE_DEVICE_POLICY_BLUETOOTH});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_SAFE_BOOT, MANAGE_DEVICE_POLICY_SAFE_BOOT);
+                UserManager.DISALLOW_CONFIG_BRIGHTNESS, new String[]{MANAGE_DEVICE_POLICY_DISPLAY});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_RUN_IN_BACKGROUND, MANAGE_DEVICE_POLICY_SAFE_BOOT);
+                UserManager.DISALLOW_CONFIG_CELL_BROADCASTS, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_REMOVE_USER, MANAGE_DEVICE_POLICY_USERS);
+                UserManager.DISALLOW_CONFIG_CREDENTIALS, new String[]{MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_PRINTING, MANAGE_DEVICE_POLICY_PRINTING);
+                UserManager.DISALLOW_CONFIG_DATE_TIME, new String[]{MANAGE_DEVICE_POLICY_TIME});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_OUTGOING_CALLS, MANAGE_DEVICE_POLICY_CALLS);
+                UserManager.DISALLOW_CONFIG_DEFAULT_APPS, new String[]{MANAGE_DEFAULT_APPLICATIONS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_OUTGOING_BEAM, MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION);
+                UserManager.DISALLOW_CONFIG_LOCALE, new String[]{MANAGE_DEVICE_POLICY_LOCALE});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_NETWORK_RESET, MANAGE_DEVICE_POLICY_MOBILE_NETWORK);
+                UserManager.DISALLOW_CONFIG_LOCATION, new String[]{MANAGE_DEVICE_POLICY_LOCATION});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA, MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA);
+                UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_MODIFY_ACCOUNTS, MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT);
+                UserManager.DISALLOW_CONFIG_PRIVATE_DNS, new String[]{MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_MICROPHONE_TOGGLE, MANAGE_DEVICE_POLICY_MICROPHONE);
+                UserManager.DISALLOW_CONFIG_SCREEN_TIMEOUT, new String[]{MANAGE_DEVICE_POLICY_DISPLAY});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY,
-                MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES);
+                UserManager.DISALLOW_CONFIG_TETHERING, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
-                MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES);
+                UserManager.DISALLOW_CONFIG_VPN, new String[]{MANAGE_DEVICE_POLICY_VPN});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_INSTALL_APPS, MANAGE_DEVICE_POLICY_APPS_CONTROL);
+                UserManager.DISALLOW_CONFIG_WIFI, new String[]{MANAGE_DEVICE_POLICY_WIFI});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_FUN, MANAGE_DEVICE_POLICY_FUN);
+                UserManager.DISALLOW_CONTENT_CAPTURE, new String[]{MANAGE_DEVICE_POLICY_SCREEN_CONTENT});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_FACTORY_RESET, MANAGE_DEVICE_POLICY_FACTORY_RESET);
+                UserManager.DISALLOW_CONTENT_SUGGESTIONS, new String[]{MANAGE_DEVICE_POLICY_SCREEN_CONTENT});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_DEBUGGING_FEATURES, MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES);
+                UserManager.DISALLOW_CREATE_WINDOWS, new String[]{MANAGE_DEVICE_POLICY_WINDOWS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_DATA_ROAMING, MANAGE_DEVICE_POLICY_MOBILE_NETWORK);
+                UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE, new String[]{MANAGE_DEVICE_POLICY_PROFILE_INTERACTION});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE,
-                MANAGE_DEVICE_POLICY_PROFILE_INTERACTION);
+                UserManager.DISALLOW_DATA_ROAMING, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CREATE_WINDOWS, MANAGE_DEVICE_POLICY_WINDOWS);
+                UserManager.DISALLOW_DEBUGGING_FEATURES, new String[]{MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONTENT_SUGGESTIONS, MANAGE_DEVICE_POLICY_SCREEN_CONTENT);
+                UserManager.DISALLOW_FACTORY_RESET, new String[]{MANAGE_DEVICE_POLICY_FACTORY_RESET});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONTENT_CAPTURE, MANAGE_DEVICE_POLICY_SCREEN_CONTENT);
+                UserManager.DISALLOW_FUN, new String[]{MANAGE_DEVICE_POLICY_FUN});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_WIFI, MANAGE_DEVICE_POLICY_WIFI);
+                UserManager.DISALLOW_INSTALL_APPS, new String[]{MANAGE_DEVICE_POLICY_APPS_CONTROL});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_VPN, MANAGE_DEVICE_POLICY_VPN);
+                UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, new String[]{MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_TETHERING, MANAGE_DEVICE_POLICY_MOBILE_NETWORK);
+                UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, new String[]{MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_SCREEN_TIMEOUT, MANAGE_DEVICE_POLICY_DISPLAY);
+                UserManager.DISALLOW_MICROPHONE_TOGGLE, new String[]{MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_PRIVATE_DNS, MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS);
+                UserManager.DISALLOW_MODIFY_ACCOUNTS, new String[]{MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, MANAGE_DEVICE_POLICY_MOBILE_NETWORK);
+                UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA, new String[]{MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_LOCATION, MANAGE_DEVICE_POLICY_LOCATION);
+                UserManager.DISALLOW_NETWORK_RESET, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_LOCALE, MANAGE_DEVICE_POLICY_LOCALE);
+                UserManager.DISALLOW_OUTGOING_BEAM, new String[]{MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_DATE_TIME, MANAGE_DEVICE_POLICY_TIME);
+                UserManager.DISALLOW_OUTGOING_CALLS, new String[]{MANAGE_DEVICE_POLICY_CALLS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_CREDENTIALS, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS);
+                UserManager.DISALLOW_PRINTING, new String[]{MANAGE_DEVICE_POLICY_PRINTING});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_CELL_BROADCASTS, MANAGE_DEVICE_POLICY_MOBILE_NETWORK);
+                UserManager.DISALLOW_REMOVE_USER, new String[]{MANAGE_DEVICE_POLICY_USERS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_BRIGHTNESS, MANAGE_DEVICE_POLICY_DISPLAY);
+                UserManager.DISALLOW_RUN_IN_BACKGROUND, new String[]{MANAGE_DEVICE_POLICY_RUN_IN_BACKGROUND});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_BLUETOOTH, MANAGE_DEVICE_POLICY_BLUETOOTH);
+                UserManager.DISALLOW_SAFE_BOOT, new String[]{MANAGE_DEVICE_POLICY_SAFE_BOOT});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CHANGE_WIFI_STATE, MANAGE_DEVICE_POLICY_WIFI);
+                UserManager.DISALLOW_SET_USER_ICON, new String[]{MANAGE_DEVICE_POLICY_USERS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CAMERA_TOGGLE, MANAGE_DEVICE_POLICY_CAMERA);
+                UserManager.DISALLOW_SET_WALLPAPER, new String[]{MANAGE_DEVICE_POLICY_WALLPAPER});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CAMERA, MANAGE_DEVICE_POLICY_CAMERA);
+                UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, new String[]{MANAGE_DEVICE_POLICY_PROFILE_INTERACTION});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_BLUETOOTH_SHARING, MANAGE_DEVICE_POLICY_BLUETOOTH);
+                UserManager.DISALLOW_SHARE_LOCATION, new String[]{MANAGE_DEVICE_POLICY_LOCATION});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_BLUETOOTH, MANAGE_DEVICE_POLICY_BLUETOOTH);
+                UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, new String[]{MANAGE_DEVICE_POLICY_WIFI});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_BIOMETRIC, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS);
+                UserManager.DISALLOW_SMS, new String[]{MANAGE_DEVICE_POLICY_SMS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_AUTOFILL, MANAGE_DEVICE_POLICY_AUTOFILL);
+                UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS, new String[]{MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_APPS_CONTROL, MANAGE_DEVICE_POLICY_APPS_CONTROL);
+                UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO, new String[]{MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_AMBIENT_DISPLAY, MANAGE_DEVICE_POLICY_DISPLAY);
+                UserManager.DISALLOW_UNIFIED_PASSWORD, new String[]{MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_AIRPLANE_MODE, MANAGE_DEVICE_POLICY_AIRPLANE_MODE);
+                UserManager.DISALLOW_UNINSTALL_APPS, new String[]{MANAGE_DEVICE_POLICY_APPS_CONTROL});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_ADJUST_VOLUME, MANAGE_DEVICE_POLICY_AUDIO_OUTPUT);
+                UserManager.DISALLOW_UNMUTE_DEVICE, new String[]{MANAGE_DEVICE_POLICY_AUDIO_OUTPUT});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_ADD_WIFI_CONFIG, MANAGE_DEVICE_POLICY_WIFI);
+                UserManager.DISALLOW_UNMUTE_MICROPHONE, new String[]{MANAGE_DEVICE_POLICY_MICROPHONE});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_ADD_USER, MANAGE_DEVICE_POLICY_USERS);
+                UserManager.DISALLOW_USB_FILE_TRANSFER, new String[]{MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_ADD_CLONE_PROFILE, MANAGE_DEVICE_POLICY_PROFILES);
+                UserManager.DISALLOW_USER_SWITCH, new String[]{MANAGE_DEVICE_POLICY_USERS});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.ALLOW_PARENT_PROFILE_APP_LINKING, MANAGE_DEVICE_POLICY_PROFILES);
+                UserManager.DISALLOW_WIFI_DIRECT, new String[]{MANAGE_DEVICE_POLICY_WIFI});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CELLULAR_2G, MANAGE_DEVICE_POLICY_MOBILE_NETWORK);
+                UserManager.DISALLOW_WIFI_TETHERING, new String[]{MANAGE_DEVICE_POLICY_WIFI});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO,
-                MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION);
+                UserManager.ENSURE_VERIFY_APPS, new String[]{MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES});
 
         // Restrictions not allowed to be set by admins.
         USER_RESTRICTION_PERMISSIONS.put(
                 UserManager.DISALLOW_RECORD_AUDIO, null);
         USER_RESTRICTION_PERMISSIONS.put(
                 UserManager.DISALLOW_WALLPAPER, null);
-        USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_CONFIG_DEFAULT_APPS, null);
     }
 
     private EnforcingAdmin enforcePermissionForUserRestriction(ComponentName who,
             String userRestriction, String callerPackageName, int userId) {
-        String permission = USER_RESTRICTION_PERMISSIONS.get(userRestriction);
-        if (permission != null) {
-            return enforcePermissionAndGetEnforcingAdmin(who, permission, callerPackageName,
-                    userId);
-        }
+        String[] permissions = USER_RESTRICTION_PERMISSIONS.get(userRestriction);
+        if (permissions.length > 0) {
+            try {
+                return enforcePermissionsAndGetEnforcingAdmin(who, permissions, callerPackageName,
+                        userId);
+            } catch (SecurityException e) {
+                throw new SecurityException("Caller does not hold the required permission for this "
+                        + "user restriction: " + userRestriction + ".\n" + e.getMessage());
+            }
+         }
         throw new SecurityException("Admins are not permitted to set User Restriction: "
                 + userRestriction);
     }
@@ -14017,9 +14071,12 @@
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
 
         if (isPolicyEngineForFinanceFlagEnabled()) {
-            EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+            EnforcingAdmin enforcingAdmin = enforcePermissionsAndGetEnforcingAdmin(
                     who,
-                    MANAGE_DEVICE_POLICY_APPS_CONTROL,
+                    new String[]{
+                            MANAGE_DEVICE_POLICY_APPS_CONTROL,
+                            MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL
+                    },
                     caller.getPackageName(),
                     caller.getUserId());
             mDevicePolicyEngine.setLocalPolicy(
@@ -16055,6 +16112,7 @@
         } else {
             long ident = mInjector.binderClearCallingIdentity();
             try {
+                // TODO(b/277908283): check in the policy engine instead of calling user manager.
                 List<UserManager.EnforcingUser> sources = mUserManager
                         .getUserRestrictionSources(restriction, UserHandle.of(userId));
                 if (sources == null) {
@@ -16086,27 +16144,14 @@
                 }
                 final UserManager.EnforcingUser enforcingUser = sources.get(0);
                 final int sourceType = enforcingUser.getUserRestrictionSource();
-                final int enforcingUserId = enforcingUser.getUserHandle().getIdentifier();
-                if (sourceType == UserManager.RESTRICTION_SOURCE_PROFILE_OWNER) {
-                    // Restriction was enforced by PO
-                    final ComponentName profileOwner = mOwners.getProfileOwnerComponent(
-                            enforcingUserId);
-                    if (profileOwner != null) {
+                if (sourceType == UserManager.RESTRICTION_SOURCE_PROFILE_OWNER
+                        || sourceType == UserManager.RESTRICTION_SOURCE_DEVICE_OWNER ) {
+                    ActiveAdmin admin = getMostProbableDPCAdminForLocalPolicy(userId);
+                    if (admin != null) {
                         result = new Bundle();
-                        result.putInt(Intent.EXTRA_USER_ID, enforcingUserId);
+                        result.putInt(Intent.EXTRA_USER_ID, admin.getUserHandle().getIdentifier());
                         result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
-                                profileOwner);
-                        return result;
-                    }
-                } else if (sourceType == UserManager.RESTRICTION_SOURCE_DEVICE_OWNER) {
-                    // Restriction was enforced by DO
-                    final Pair<Integer, ComponentName> deviceOwner =
-                            mOwners.getDeviceOwnerUserIdAndComponent();
-                    if (deviceOwner != null) {
-                        result = new Bundle();
-                        result.putInt(Intent.EXTRA_USER_ID, deviceOwner.first);
-                        result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
-                                deviceOwner.second);
+                                admin.info.getComponent());
                         return result;
                     }
                 } else if (sourceType == UserManager.RESTRICTION_SOURCE_SYSTEM) {
@@ -16626,10 +16671,6 @@
         SENSOR_PERMISSIONS.add(Manifest.permission.BACKGROUND_CAMERA);
         SENSOR_PERMISSIONS.add(Manifest.permission.RECORD_BACKGROUND_AUDIO);
         SENSOR_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND);
-        SENSOR_PERMISSIONS.add(
-                Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE);
-        SENSOR_PERMISSIONS.add(
-                    Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND);
     }
 
     private boolean canGrantPermission(CallerIdentity caller, String permission,
@@ -22422,6 +22463,14 @@
         });
     }
 
+    // Permission that will need to be created in V.
+    private static final String MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL =
+            "manage_device_policy_block_uninstall";
+    private static final String MANAGE_DEVICE_POLICY_CAMERA_TOGGLE =
+            "manage_device_policy_camera_toggle";
+    private static final String MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE =
+            "manage_device_policy_microphone_toggle";
+
     // DPC types
     private static final int DEFAULT_DEVICE_OWNER = 0;
     private static final int FINANCED_DEVICE_OWNER = 1;
@@ -22512,53 +22561,53 @@
     );
 
     /**
-     * All the permisisons granted to a profile owner.
+     * All the permissions granted to a profile owner.
      */
     private static final List<String> PROFILE_OWNER_PERMISSIONS  =
             List.of(
-                MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT,
-                MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL,
-                MANAGE_DEVICE_POLICY_APPS_CONTROL,
-                MANAGE_DEVICE_POLICY_APP_RESTRICTIONS,
-                MANAGE_DEVICE_POLICY_AUDIO_OUTPUT,
-                MANAGE_DEVICE_POLICY_AUTOFILL,
-                MANAGE_DEVICE_POLICY_CALLS,
-                MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES,
-                MANAGE_DEVICE_POLICY_DISPLAY,
-                MANAGE_DEVICE_POLICY_FACTORY_RESET,
-                MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES,
-                MANAGE_DEVICE_POLICY_KEYGUARD,
-                MANAGE_DEVICE_POLICY_LOCALE,
-                MANAGE_DEVICE_POLICY_LOCATION,
-                MANAGE_DEVICE_POLICY_LOCK,
-                MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
-                MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION,
-                MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
-                MANAGE_DEVICE_POLICY_PACKAGE_STATE,
-                MANAGE_DEVICE_POLICY_PRINTING,
-                MANAGE_DEVICE_POLICY_PROFILES,
-                MANAGE_DEVICE_POLICY_PROFILE_INTERACTION,
-                MANAGE_DEVICE_POLICY_RESET_PASSWORD,
-                MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS,
-                MANAGE_DEVICE_POLICY_SCREEN_CAPTURE,
-                MANAGE_DEVICE_POLICY_SCREEN_CONTENT,
-                MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE,
-                MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS,
-                MANAGE_DEVICE_POLICY_TIME,
-                MANAGE_DEVICE_POLICY_VPN,
-                MANAGE_DEVICE_POLICY_WIPE_DATA
+                    MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT,
+                    MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL,
+                    MANAGE_DEVICE_POLICY_APPS_CONTROL,
+                    MANAGE_DEVICE_POLICY_APP_RESTRICTIONS,
+                    MANAGE_DEVICE_POLICY_AUDIO_OUTPUT,
+                    MANAGE_DEVICE_POLICY_AUTOFILL,
+                    MANAGE_DEVICE_POLICY_BLUETOOTH,
+                    MANAGE_DEVICE_POLICY_CALLS,
+                    MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES,
+                    MANAGE_DEVICE_POLICY_DISPLAY,
+                    MANAGE_DEVICE_POLICY_FACTORY_RESET,
+                    MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES,
+                    MANAGE_DEVICE_POLICY_KEYGUARD,
+                    MANAGE_DEVICE_POLICY_LOCALE,
+                    MANAGE_DEVICE_POLICY_LOCATION,
+                    MANAGE_DEVICE_POLICY_LOCK,
+                    MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
+                    MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION,
+                    MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
+                    MANAGE_DEVICE_POLICY_PACKAGE_STATE,
+                    MANAGE_DEVICE_POLICY_PRINTING,
+                    MANAGE_DEVICE_POLICY_PROFILES,
+                    MANAGE_DEVICE_POLICY_PROFILE_INTERACTION,
+                    MANAGE_DEVICE_POLICY_RESET_PASSWORD,
+                    MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS,
+                    MANAGE_DEVICE_POLICY_SCREEN_CAPTURE,
+                    MANAGE_DEVICE_POLICY_SCREEN_CONTENT,
+                    MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE,
+                    MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS,
+                    MANAGE_DEVICE_POLICY_TIME,
+                    MANAGE_DEVICE_POLICY_VPN,
+                    MANAGE_DEVICE_POLICY_WIPE_DATA
             );
 
     /**
-    * All the additional permissions granted to an organisation owned profile owner.
-    */
+     * All the additional permissions granted to an organisation owned profile owner.
+     */
     private static final List<String>
             ADDITIONAL_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE_PERMISSIONS =
-                List.of(
+            List.of(
                     MANAGE_DEVICE_POLICY_ACROSS_USERS,
                     MANAGE_DEVICE_POLICY_AIRPLANE_MODE,
                     MANAGE_DEVICE_POLICY_APPS_CONTROL,
-                    MANAGE_DEVICE_POLICY_BLUETOOTH,
                     MANAGE_DEVICE_POLICY_CAMERA,
                     MANAGE_DEVICE_POLICY_CERTIFICATES,
                     MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE,
@@ -22579,13 +22628,12 @@
                     MANAGE_DEVICE_POLICY_WIFI,
                     SET_TIME,
                     SET_TIME_ZONE
-                );
+            );
 
 
     private static final List<String> ADDITIONAL_PROFILE_OWNER_ON_USER_0_PERMISSIONS =
             List.of(
                     MANAGE_DEVICE_POLICY_AIRPLANE_MODE,
-                    MANAGE_DEVICE_POLICY_BLUETOOTH,
                     MANAGE_DEVICE_POLICY_CAMERA,
                     MANAGE_DEVICE_POLICY_DISPLAY,
                     MANAGE_DEVICE_POLICY_FUN,
@@ -22645,7 +22693,7 @@
     {
         DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, DELEGATION_PERMISSION_GRANT);
         DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_APP_RESTRICTIONS, DELEGATION_APP_RESTRICTIONS);
-        DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_APPS_CONTROL, DELEGATION_BLOCK_UNINSTALL);
+        DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL, DELEGATION_BLOCK_UNINSTALL);
         DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, DELEGATION_SECURITY_LOGGING);
         DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_PACKAGE_STATE, DELEGATION_PACKAGE_ACCESS);
     }
@@ -22724,6 +22772,10 @@
                 MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_AUTOFILL,
                 MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+        CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL,
+                MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+        CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_CAMERA_TOGGLE,
+                MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE,
                 MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES,
@@ -22740,6 +22792,8 @@
                 MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK_TASK,
                 MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+        CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE,
+                MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
                 MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_PROFILES,
@@ -22770,13 +22824,34 @@
 
     /**
      * Checks if the calling process has been granted permission to apply a device policy on a
+     * specific user. Only one permission provided in the list needs to be granted to pass this
+     * check.
+     * The given permissions will be checked along with their associated cross-user permissions if
+     * they exists and the target user is different to the calling user.
+     * Returns an {@link EnforcingAdmin} for the caller.
+     *
+     * @param admin the component name of the admin.
+     * @param callerPackageName The package name of the calling application.
+     * @param permissions an array of permission names to be checked.
+     * @param targetUserId The userId of the user which the caller needs permission to act on.
+     * @throws SecurityException if the caller has not been granted the given permission,
+     * the associated cross-user permission if the caller's user is different to the target user.
+     */
+    private EnforcingAdmin enforcePermissionsAndGetEnforcingAdmin(@Nullable ComponentName admin,
+            String[] permissions, String callerPackageName, int targetUserId) {
+        enforcePermissions(permissions, callerPackageName, targetUserId);
+        return getEnforcingAdminForCaller(admin, callerPackageName);
+    }
+
+    /**
+     * Checks if the calling process has been granted permission to apply a device policy on a
      * specific user.
      * The given permission will be checked along with its associated cross-user permission if it
      * exists and the target user is different to the calling user.
      * Returns an {@link EnforcingAdmin} for the caller.
      *
      * @param admin the component name of the admin.
-     * @param callerPackageName The package name  of the calling application.
+     * @param callerPackageName The package name of the calling application.
      * @param permission The name of the permission being checked.
      * @param targetUserId The userId of the user which the caller needs permission to act on.
      * @throws SecurityException if the caller has not been granted the given permission,
@@ -22834,32 +22909,24 @@
             new HashMap<>();
 
     /**
-     * Checks if the calling process has been granted permission to apply a device policy on a
-     * specific user.
-     * The given permission will be checked along with its associated cross-user permission if it
-     * exists and the target user is different to the calling user.
+     * Checks if the calling process has been granted permission to apply a device policy.
      *
      * @param callerPackageName The package name  of the calling application.
      * @param permission The name of the permission being checked.
-     * @param targetUserId The userId of the user which the caller needs permission to act on.
      * @throws SecurityException if the caller has not been granted the given permission,
      * the associated cross-user permission if the caller's user is different to the target user.
      */
-    private void enforcePermission(String permission, String callerPackageName, int targetUserId)
+    private void enforcePermission(String permission, String callerPackageName)
             throws SecurityException {
-        if (!hasPermission(permission, callerPackageName, targetUserId)) {
-            // TODO(b/276920002): Split the error messages so that the cross-user permission
-            // is only mentioned when it is needed.
+        if (!hasPermission(permission, callerPackageName)) {
             throw new SecurityException("Caller does not have the required permissions for "
-                    + "this user. Permissions required: {"
+                    + "this user. Permission required: "
                     + permission
-                    + ", "
-                    + CROSS_USER_PERMISSIONS.get(permission)
-                    + "(if calling cross-user)"
-                    + "}");
+                    + ".");
         }
     }
 
+
     /**
      * Checks if the calling process has been granted permission to apply a device policy on a
      * specific user.
@@ -22872,23 +22939,66 @@
      * @throws SecurityException if the caller has not been granted the given permission,
      * the associated cross-user permission if the caller's user is different to the target user.
      */
-    private void enforcePermission(String permission, int adminPolicy,
-            String callerPackageName, int targetUserId)
+    private void enforcePermission(String permission, String callerPackageName, int targetUserId)
             throws SecurityException {
-        if (!hasPermissionOrAdminPolicy(permission, callerPackageName, adminPolicy, targetUserId)) {
-            // TODO(b/276920002): Split the error messages so that the cross-user permission
-            // is only mentioned when it is needed.
-            throw new SecurityException("Caller does not have the required permissions for "
-                    + "this user. Permissions required: {"
-                    + permission
-                    + ", "
-                    + CROSS_USER_PERMISSIONS.get(permission)
-                    + "(if calling cross-user)"
-                    + "}");
+        enforcePermission(permission, callerPackageName);
+        if (targetUserId != getCallerIdentity(callerPackageName).getUserId()) {
+            enforcePermission(CROSS_USER_PERMISSIONS.get(permission), callerPackageName);
         }
     }
 
     /**
+     * Checks if the calling process has been granted permission to apply a device policy on a
+     * specific user. Only one of the given permissions will be required to be held to pass this
+     * check.
+     * The given permissions will be checked along with their associated cross-user permissions if
+     * they exist and the target user is different to the calling user.
+     *
+     * @param permissions An array of the names of the permissions being checked.
+     * @param callerPackageName The package name  of the calling application.
+     * @param targetUserId The userId of the user which the caller needs permission to act on.
+     * @throws SecurityException if the caller has not been granted the given permission,
+     * the associated cross-user permission if the caller's user is different to the target user.
+     */
+    private void enforcePermissions(String[] permissions, String callerPackageName,
+            int targetUserId) throws SecurityException {
+        String heldPermission = "";
+        for (String permission : permissions) {
+            if (hasPermission(permission, callerPackageName)) {
+                heldPermission = permission;
+                break;
+            }
+        }
+        if (heldPermission.isEmpty()) {
+            throw new SecurityException("Caller does not have the required permissions for "
+                    + "this user. One of the following permission required: "
+                    + Arrays.toString(permissions));
+        }
+        enforcePermission(heldPermission, callerPackageName, targetUserId);
+    }
+
+    /**
+     * Checks if the calling process has been granted permission to apply a device policy on a
+     * specific user.
+     * The given permission will be checked along with its associated cross-user permission if it
+     * exists and the target user is different to the calling user.
+     *
+     * @param callerPackageName The package name  of the calling application.
+     * @param adminPolicy The admin policy that should grant holders permission.
+     * @param permission The name of the permission being checked.
+     * @param targetUserId The userId of the user which the caller needs permission to act on.
+     * @throws SecurityException if the caller has not been granted the given permission,
+     * the associated cross-user permission if the caller's user is different to the target user.
+     */
+    private void enforcePermission(String permission, int adminPolicy,
+            String callerPackageName, int targetUserId) throws SecurityException {
+        if (hasAdminPolicy(adminPolicy, callerPackageName)) {
+            return;
+        }
+        enforcePermission(permission, callerPackageName, targetUserId);
+    }
+
+    /**
      * Checks whether the calling process has been granted permission to query a device policy on
      * a specific user.
      * The given permission will be checked along with its associated cross-user permission if it
@@ -22909,6 +23019,12 @@
         enforcePermission(permission, callerPackageName, targetUserId);
     }
 
+    private boolean hasAdminPolicy(int adminPolicy, String callerPackageName) {
+        CallerIdentity caller = getCallerIdentity(callerPackageName);
+        ActiveAdmin deviceAdmin = getActiveAdminForCaller(null, caller);
+        return deviceAdmin != null && deviceAdmin.info.usesPolicy(adminPolicy);
+    }
+
     /**
      * Return whether the calling process has been granted permission to apply a device policy on
      * a specific user.
@@ -22921,24 +23037,15 @@
         CallerIdentity caller = getCallerIdentity(callerPackageName);
         boolean hasPermissionOnOwnUser = hasPermission(permission, caller.getPackageName());
         boolean hasPermissionOnTargetUser = true;
-        if (hasPermissionOnOwnUser & caller.getUserId() != targetUserId) {
-            hasPermissionOnTargetUser = hasPermission(CROSS_USER_PERMISSIONS.get(permission),
-                    caller.getPackageName());
+        if (hasPermissionOnOwnUser && caller.getUserId() != targetUserId) {
+            hasPermissionOnTargetUser = hasPermissionOnTargetUser
+                    && hasPermission(CROSS_USER_PERMISSIONS.get(permission),
+                        caller.getPackageName());
         }
 
         return hasPermissionOnOwnUser && hasPermissionOnTargetUser;
     }
 
-    private boolean hasPermissionOrAdminPolicy(String permission, String callerPackageName,
-            int adminPolicy, int targetUserId) {
-        CallerIdentity caller = getCallerIdentity(callerPackageName);
-        if (hasPermission(permission, caller.getPackageName(), targetUserId)) {
-            return true;
-        }
-        ActiveAdmin deviceAdmin = getActiveAdminForCaller(null, caller);
-        return deviceAdmin != null && deviceAdmin.info.usesPolicy(adminPolicy);
-    }
-
     /**
      * Return whether the calling process has been granted the given permission.
      *
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 509a66b..8c2468a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -284,6 +284,7 @@
     private static final Map<String, PolicyDefinition<?>> POLICY_DEFINITIONS = new HashMap<>();
     private static Map<String, Integer> USER_RESTRICTION_FLAGS = new HashMap<>();
 
+    // TODO(b/277218360): Revisit policies that should be marked as global-only.
     static {
         POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.AUTO_TIMEZONE_POLICY, AUTO_TIMEZONE);
         POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.PERMISSION_GRANT_POLICY,
@@ -312,8 +313,9 @@
         USER_RESTRICTION_FLAGS.put(
                 UserManager.DISALLOW_WIFI_TETHERING, POLICY_FLAG_GLOBAL_ONLY_POLICY);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_GRANT_ADMIN, /* flags= */ 0);
+        // TODO: set as global only once we get rid of the mapping
         USER_RESTRICTION_FLAGS.put(
-                UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+                UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(
                 UserManager.DISALLOW_WIFI_DIRECT, POLICY_FLAG_GLOBAL_ONLY_POLICY);
         USER_RESTRICTION_FLAGS.put(
@@ -333,8 +335,10 @@
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONFIG_BLUETOOTH, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_BLUETOOTH, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_BLUETOOTH_SHARING, /* flags= */ 0);
+        // This effectively always applies globally, but it can be set on the profile
+        // parent, check the javadocs on the restriction for more info.
         USER_RESTRICTION_FLAGS.put(
-                UserManager.DISALLOW_USB_FILE_TRANSFER, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+                UserManager.DISALLOW_USB_FILE_TRANSFER, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONFIG_CREDENTIALS, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_REMOVE_USER, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, /* flags= */ 0);
@@ -344,8 +348,10 @@
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONFIG_DATE_TIME, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(
                 UserManager.DISALLOW_CONFIG_TETHERING, /* flags= */ 0);
+        // This effectively always applies globally, but it can be set on the profile
+        // parent, check the javadocs on the restriction for more info.
         USER_RESTRICTION_FLAGS.put(
-                UserManager.DISALLOW_NETWORK_RESET, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+                UserManager.DISALLOW_NETWORK_RESET, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_FACTORY_RESET, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_ADD_USER, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_ADD_MANAGED_PROFILE, /* flags= */ 0);
@@ -376,8 +382,7 @@
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_UNMUTE_DEVICE, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_DATA_ROAMING, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_SET_USER_ICON, /* flags= */ 0);
-            // TODO: double check flags
-        USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_OEM_UNLOCK, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+        USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_OEM_UNLOCK, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_UNIFIED_PASSWORD, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.ALLOW_PARENT_PROFILE_APP_LINKING, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_AUTOFILL, /* flags= */ 0);
@@ -390,6 +395,7 @@
         USER_RESTRICTION_FLAGS.put(
                 UserManager.DISALLOW_CONFIG_PRIVATE_DNS, POLICY_FLAG_GLOBAL_ONLY_POLICY);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_MICROPHONE_TOGGLE, /* flags= */ 0);
+        // TODO: According the UserRestrictionsUtils, this is global only, need to confirm.
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CAMERA_TOGGLE, /* flags= */ 0);
         // TODO: check if its global only
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_BIOMETRIC, /* flags= */ 0);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 492d477..b1d6131 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -125,6 +125,7 @@
 import com.android.server.connectivity.PacProxyService;
 import com.android.server.contentcapture.ContentCaptureManagerInternal;
 import com.android.server.coverage.CoverageService;
+import com.android.server.cpu.CpuMonitorService;
 import com.android.server.devicepolicy.DevicePolicyManagerService;
 import com.android.server.devicestate.DeviceStateManagerService;
 import com.android.server.display.DisplayManagerService;
@@ -1405,6 +1406,15 @@
         mSystemServiceManager.startService(RemoteProvisioningService.class);
         t.traceEnd();
 
+        // TODO(b/277600174): Start CpuMonitorService on all builds and not just on debuggable
+        // builds once the Android JobScheduler starts using this service.
+        if (Build.IS_DEBUGGABLE || Build.IS_ENG) {
+          // Service for CPU monitor.
+          t.traceBegin("CpuMonitorService");
+          mSystemServiceManager.startService(CpuMonitorService.class);
+          t.traceEnd();
+        }
+
         t.traceEnd(); // startCoreServices
     }
 
diff --git a/services/tests/PackageManagerServiceTests/TEST_MAPPING b/services/tests/PackageManagerServiceTests/TEST_MAPPING
index e98acb2..5d96af9 100644
--- a/services/tests/PackageManagerServiceTests/TEST_MAPPING
+++ b/services/tests/PackageManagerServiceTests/TEST_MAPPING
@@ -55,23 +55,10 @@
           // TODO(b/204133664)
           "exclude-filter": "com.android.server.pm.test.SdCardEjectionTests"
         },
-	{
-          // TODO(b/272575212)
-          "exclude-filter": "com.android.server.pm.test.SettingsTest#testWriteCorruptDataBinaryXml"
-	},
-	{
-          "exclude-filter": "com.android.server.pm.test.SettingsTest#testWriteCorruptDataTextXml"
-	},
-	{
-          "exclude-filter": "com.android.server.pm.test.SettingsTest#testWriteCorruptHeaderBinaryXml"
-	},
-	{
-          "exclude-filter": "com.android.server.pm.test.SettingsTest#testWriteCorruptHeaderTextXml"
-	},
-	{
+        {
           // TODO(b/272714903)
           "exclude-filter": "com.android.server.pm.test.OverlayPathsUninstallSystemUpdatesTest#verify"
-	}
+        }
       ]
     }
   ],
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index 1a75170..7b771af 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -567,36 +567,36 @@
 
         // Ensure that no action is taken for cases where the failure reason is unknown
         assertEquals(observer.onHealthCheckFailed(null, PackageWatchdog.FAILURE_REASON_UNKNOWN, 1),
-                PackageHealthObserverImpact.USER_IMPACT_NONE);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_0);
 
         // Ensure the correct user impact is returned for each mitigation count.
         assertEquals(observer.onHealthCheckFailed(null,
                 PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1),
-                PackageHealthObserverImpact.USER_IMPACT_LOW);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
 
         assertEquals(observer.onHealthCheckFailed(null,
                 PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 2),
-                PackageHealthObserverImpact.USER_IMPACT_LOW);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
 
         assertEquals(observer.onHealthCheckFailed(null,
                 PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 3),
-                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
 
         assertEquals(observer.onHealthCheckFailed(null,
                 PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4),
-                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
     }
 
     @Test
     public void testBootLoopLevels() {
         RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
 
-        assertEquals(observer.onBootLoop(0), PackageHealthObserverImpact.USER_IMPACT_NONE);
-        assertEquals(observer.onBootLoop(1), PackageHealthObserverImpact.USER_IMPACT_LOW);
-        assertEquals(observer.onBootLoop(2), PackageHealthObserverImpact.USER_IMPACT_LOW);
-        assertEquals(observer.onBootLoop(3), PackageHealthObserverImpact.USER_IMPACT_HIGH);
-        assertEquals(observer.onBootLoop(4), PackageHealthObserverImpact.USER_IMPACT_HIGH);
-        assertEquals(observer.onBootLoop(5), PackageHealthObserverImpact.USER_IMPACT_HIGH);
+        assertEquals(observer.onBootLoop(0), PackageHealthObserverImpact.USER_IMPACT_LEVEL_0);
+        assertEquals(observer.onBootLoop(1), PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
+        assertEquals(observer.onBootLoop(2), PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
+        assertEquals(observer.onBootLoop(3), PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
+        assertEquals(observer.onBootLoop(4), PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
+        assertEquals(observer.onBootLoop(5), PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
     }
 
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 8211d6f..3fb7fb4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -74,16 +74,17 @@
 import android.os.DropBoxManager;
 import android.os.HandlerThread;
 import android.os.SystemClock;
+import android.os.TestLooperManager;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.IndentingPrintWriter;
 import android.util.Pair;
 
 import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.ExtendedMockitoRule;
-import com.android.server.am.BroadcastQueueTest.SyncBarrier;
 
 import org.junit.After;
 import org.junit.Before;
@@ -96,6 +97,7 @@
 import java.lang.reflect.Array;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 @SmallTest
 public final class BroadcastQueueModernImplTest {
@@ -111,6 +113,7 @@
     @Mock BroadcastProcessQueue mQueue4;
 
     HandlerThread mHandlerThread;
+    TestLooperManager mLooper;
 
     BroadcastConstants mConstants;
     BroadcastQueueModernImpl mImpl;
@@ -127,6 +130,10 @@
         mHandlerThread = new HandlerThread(getClass().getSimpleName());
         mHandlerThread.start();
 
+        // Pause all event processing until a test chooses to resume
+        mLooper = Objects.requireNonNull(InstrumentationRegistry.getInstrumentation()
+                .acquireLooperManager(mHandlerThread.getLooper()));
+
         mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS);
         mConstants.DELAY_URGENT_MILLIS = -120_000;
         mConstants.DELAY_NORMAL_MILLIS = 10_000;
@@ -167,6 +174,17 @@
         mHandlerThread.quit();
     }
 
+    /**
+     * Un-pause our handler to process pending events, wait for our queue to go
+     * idle, and then re-pause the handler.
+     */
+    private void waitForIdle() throws Exception {
+        mLooper.release();
+        mImpl.waitForIdle(LOG_WRITER_INFO);
+        mLooper = Objects.requireNonNull(InstrumentationRegistry.getInstrumentation()
+                .acquireLooperManager(mHandlerThread.getLooper()));
+    }
+
     private static void assertOrphan(BroadcastProcessQueue queue) {
         assertNull(queue.runnableAtNext);
         assertNull(queue.runnableAtPrev);
@@ -237,9 +255,23 @@
     }
 
     private void enqueueOrReplaceBroadcast(BroadcastProcessQueue queue,
+            BroadcastRecord record, int recordIndex) {
+        enqueueOrReplaceBroadcast(queue, record, recordIndex, false, 42_000_000L);
+    }
+
+    private void enqueueOrReplaceBroadcast(BroadcastProcessQueue queue,
             BroadcastRecord record, int recordIndex, long enqueueTime) {
-        queue.enqueueOrReplaceBroadcast(record, recordIndex, false);
+        enqueueOrReplaceBroadcast(queue, record, recordIndex, false, enqueueTime);
+    }
+
+    private void enqueueOrReplaceBroadcast(BroadcastProcessQueue queue,
+            BroadcastRecord record, int recordIndex, boolean wouldBeSkipped, long enqueueTime) {
+        queue.enqueueOrReplaceBroadcast(record, recordIndex, wouldBeSkipped, (r, i) -> {
+            throw new UnsupportedOperationException();
+        });
         record.enqueueTime = enqueueTime;
+        record.enqueueRealTime = enqueueTime;
+        record.enqueueClockTime = enqueueTime;
     }
 
     @Test
@@ -370,7 +402,7 @@
                 .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
         final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, options,
                 List.of(makeMockRegisteredReceiver()), false);
-        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false);
+        enqueueOrReplaceBroadcast(queue, airplaneRecord, 0);
 
         queue.setProcessAndUidCached(null, false);
         final long notCachedRunnableAt = queue.getRunnableAt();
@@ -397,7 +429,7 @@
                 .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_NONE);
         final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, options,
                 List.of(makeMockRegisteredReceiver()), false);
-        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false);
+        enqueueOrReplaceBroadcast(queue, airplaneRecord, 0);
 
         queue.setProcessAndUidCached(null, false);
         final long notCachedRunnableAt = queue.getRunnableAt();
@@ -421,12 +453,12 @@
         // enqueue a bg-priority broadcast then a fg-priority one
         final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
         final BroadcastRecord timezoneRecord = makeBroadcastRecord(timezone);
-        queue.enqueueOrReplaceBroadcast(timezoneRecord, 0, false);
+        enqueueOrReplaceBroadcast(queue, timezoneRecord, 0);
 
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         airplane.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
-        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false);
+        enqueueOrReplaceBroadcast(queue, airplaneRecord, 0);
 
         // verify that:
         // (a) the queue is immediately runnable by existence of a fg-priority broadcast
@@ -457,13 +489,14 @@
         final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, null,
                 List.of(withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10),
                         withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 0)), true);
-        queue.enqueueOrReplaceBroadcast(airplaneRecord, 1, false);
+        enqueueOrReplaceBroadcast(queue, airplaneRecord, 1);
 
         assertFalse(queue.isRunnable());
         assertEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason());
 
         // Bumping past barrier makes us now runnable
-        airplaneRecord.terminalCount++;
+        airplaneRecord.setDeliveryState(0, BroadcastRecord.DELIVERY_DELIVERED,
+                "testRunnableAt_Ordered");
         queue.invalidateRunnableAt();
         assertTrue(queue.isRunnable());
         assertNotEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason());
@@ -480,7 +513,7 @@
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane,
                 List.of(makeMockRegisteredReceiver()));
-        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false);
+        enqueueOrReplaceBroadcast(queue, airplaneRecord, 0);
 
         mConstants.MAX_PENDING_BROADCASTS = 128;
         queue.invalidateRunnableAt();
@@ -506,11 +539,11 @@
                 new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED),
                 List.of(makeMockRegisteredReceiver()));
 
-        queue.enqueueOrReplaceBroadcast(lazyRecord, 0, false);
+        enqueueOrReplaceBroadcast(queue, lazyRecord, 0);
         assertThat(queue.getRunnableAt()).isGreaterThan(lazyRecord.enqueueTime);
         assertThat(queue.getRunnableAtReason()).isNotEqualTo(testRunnableAtReason);
 
-        queue.enqueueOrReplaceBroadcast(testRecord, 0, false);
+        enqueueOrReplaceBroadcast(queue, testRecord, 0);
         assertThat(queue.getRunnableAt()).isAtMost(testRecord.enqueueTime);
         assertThat(queue.getRunnableAtReason()).isEqualTo(testRunnableAtReason);
     }
@@ -572,22 +605,22 @@
         BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
                 PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
 
-        queue.enqueueOrReplaceBroadcast(
+        enqueueOrReplaceBroadcast(queue,
                 makeBroadcastRecord(new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED)
-                        .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, false);
-        queue.enqueueOrReplaceBroadcast(
-                makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0, false);
-        queue.enqueueOrReplaceBroadcast(
+                        .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0);
+        enqueueOrReplaceBroadcast(queue,
                 makeBroadcastRecord(new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED)
-                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, false);
-        queue.enqueueOrReplaceBroadcast(
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
+        enqueueOrReplaceBroadcast(queue,
                 makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)
-                        .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, false);
-        queue.enqueueOrReplaceBroadcast(
-                makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0, false);
-        queue.enqueueOrReplaceBroadcast(
+                        .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0);
+        enqueueOrReplaceBroadcast(queue,
                 makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED)
-                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, false);
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
 
         queue.makeActiveNextPending();
         assertEquals(Intent.ACTION_LOCKED_BOOT_COMPLETED, queue.getActive().intent.getAction());
@@ -821,9 +854,6 @@
         optionsAlarmVolumeChanged.setDeliveryGroupMatchingKey("audio",
                 String.valueOf(AudioManager.STREAM_ALARM));
 
-        // Halt all processing so that we get a consistent view
-        mHandlerThread.getLooper().getQueue().postSyncBarrier();
-
         mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick));
         mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged,
                 optionsMusicVolumeChanged));
@@ -890,9 +920,6 @@
                 String.valueOf(TEST_UID2));
         optionsPackageChangedForUid.setDeliveryGroupExtrasMerger(extrasMerger);
 
-        // Halt all processing so that we get a consistent view
-        mHandlerThread.getLooper().getQueue().postSyncBarrier();
-
         mImpl.enqueueBroadcastLocked(makeBroadcastRecord(packageChangedForUid,
                 optionsPackageChangedForUid));
         mImpl.enqueueBroadcastLocked(makeBroadcastRecord(packageChangedForUid2,
@@ -940,9 +967,6 @@
                 BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
         optionsAlarmVolumeChanged.setDeliveryGroupMatchingFilter(filterAlarmVolumeChanged);
 
-        // Halt all processing so that we get a consistent view
-        mHandlerThread.getLooper().getQueue().postSyncBarrier();
-
         mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick));
         mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged,
                 optionsMusicVolumeChanged));
@@ -989,9 +1013,6 @@
         final Pair<Intent, BroadcastOptions> dropboxEntryBroadcast3 = createDropboxBroadcast(
                 "TAG_A", now + 2000, 7);
 
-        // Halt all processing so that we get a consistent view
-        mHandlerThread.getLooper().getQueue().postSyncBarrier();
-
         mImpl.enqueueBroadcastLocked(makeBroadcastRecord(dropboxEntryBroadcast1.first,
                 dropboxEntryBroadcast1.second));
         mImpl.enqueueBroadcastLocked(makeBroadcastRecord(dropboxEntryBroadcast2.first,
@@ -1021,9 +1042,6 @@
                 .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
                 .setDeliveryGroupMatchingKey(Intent.ACTION_CLOSE_SYSTEM_DIALOGS, "testing");
 
-        // Halt all processing so that we get a consistent view
-        mHandlerThread.getLooper().getQueue().postSyncBarrier();
-
         mImpl.enqueueBroadcastLocked(makeBroadcastRecord(
                 closeSystemDialogs1, optionsCloseSystemDialog1));
         mImpl.enqueueBroadcastLocked(makeBroadcastRecord(
@@ -1073,9 +1091,6 @@
         final Intent userPresent = new Intent(Intent.ACTION_USER_PRESENT);
         userPresent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
 
-        // Halt all processing so that we get a consistent view
-        mHandlerThread.getLooper().getQueue().postSyncBarrier();
-
         final BroadcastRecord userPresentRecord1 = makeBroadcastRecord(userPresent);
         final BroadcastRecord userPresentRecord2 = makeBroadcastRecord(userPresent);
 
@@ -1114,8 +1129,6 @@
                 makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW)
         ));
 
-        // Halt all processing so that we get a consistent view
-        mHandlerThread.getLooper().getQueue().postSyncBarrier();
         mImpl.enqueueBroadcastLocked(record1);
         mImpl.enqueueBroadcastLocked(record2);
 
@@ -1137,12 +1150,9 @@
         final BroadcastOptions optionsTimeTick = BroadcastOptions.makeBasic();
         optionsTimeTick.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
 
-        // Halt all processing so that we get a consistent view
-        try (SyncBarrier b = new SyncBarrier(mHandlerThread)) {
-            mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick));
-            mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick));
-        }
-        mImpl.waitForIdle(LOG_WRITER_INFO);
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick));
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick));
+        waitForIdle();
 
         // Verify that there is only one delivery event reported since one of the broadcasts
         // should have been skipped.
@@ -1163,8 +1173,8 @@
 
         final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK)
                 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        queue.enqueueOrReplaceBroadcast(makeBroadcastRecord(timeTick,
-                List.of(makeMockRegisteredReceiver())), 0, false);
+        enqueueOrReplaceBroadcast(queue, makeBroadcastRecord(timeTick,
+                List.of(makeMockRegisteredReceiver())), 0);
         assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked());
 
         // Make the foreground broadcast as active.
@@ -1175,15 +1185,15 @@
         assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked());
 
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
-        queue.enqueueOrReplaceBroadcast(makeBroadcastRecord(airplane,
-                List.of(makeMockRegisteredReceiver())), 0, false);
+        enqueueOrReplaceBroadcast(queue, makeBroadcastRecord(airplane,
+                List.of(makeMockRegisteredReceiver())), 0);
 
         // Make the background broadcast as active.
         queue.makeActiveNextPending();
         assertEquals(ProcessList.SCHED_GROUP_BACKGROUND, queue.getPreferredSchedulingGroupLocked());
 
-        queue.enqueueOrReplaceBroadcast(makeBroadcastRecord(timeTick,
-                List.of(makeMockRegisteredReceiver())), 0, false);
+        enqueueOrReplaceBroadcast(queue, makeBroadcastRecord(timeTick,
+                List.of(makeMockRegisteredReceiver())), 0);
         // Even though the active broadcast is not a foreground one, scheduling group will be
         // DEFAULT since there is a foreground broadcast waiting to be delivered.
         assertEquals(ProcessList.SCHED_GROUP_DEFAULT, queue.getPreferredSchedulingGroupLocked());
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index b6bc02a..7be1d7c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.am;
 
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
 import static android.os.UserHandle.USER_SYSTEM;
 
 import static com.android.server.am.ActivityManagerDebugConfig.LOG_WRITER_INFO;
@@ -39,7 +41,6 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -76,6 +77,7 @@
 import android.os.IBinder;
 import android.os.PowerExemptionManager;
 import android.os.SystemClock;
+import android.os.TestLooperManager;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Log;
@@ -145,6 +147,7 @@
 
     private Context mContext;
     private HandlerThread mHandlerThread;
+    private TestLooperManager mLooper;
     private AtomicInteger mNextPid;
 
     @Mock
@@ -205,6 +208,11 @@
 
         mHandlerThread = new HandlerThread(TAG);
         mHandlerThread.start();
+
+        // Pause all event processing until a test chooses to resume
+        mLooper = Objects.requireNonNull(InstrumentationRegistry.getInstrumentation()
+                .acquireLooperManager(mHandlerThread.getLooper()));
+
         mNextPid = new AtomicInteger(100);
 
         LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
@@ -359,25 +367,6 @@
         }
     }
 
-    /**
-     * Helper that leverages try-with-resources to pause dispatch of
-     * {@link #mHandlerThread} until released.
-     */
-    static class SyncBarrier implements AutoCloseable {
-        private final int mToken;
-        private HandlerThread mThread;
-
-        SyncBarrier(HandlerThread thread) {
-            mThread = thread;
-            mToken = mThread.getLooper().getQueue().postSyncBarrier();
-        }
-
-        @Override
-        public void close() throws Exception {
-            mThread.getLooper().getQueue().removeSyncBarrier(mToken);
-        }
-    }
-
     private enum ProcessStartBehavior {
         /** Process starts successfully */
         SUCCESS,
@@ -463,7 +452,7 @@
 
         doAnswer((invocation) -> {
             Log.v(TAG, "Intercepting scheduleReceiver() for "
-                    + Arrays.toString(invocation.getArguments()));
+                    + Arrays.toString(invocation.getArguments()) + " package " + ai.packageName);
             assertHealth();
             final Intent intent = invocation.getArgument(0);
             final Bundle extras = invocation.getArgument(5);
@@ -485,7 +474,7 @@
 
         doAnswer((invocation) -> {
             Log.v(TAG, "Intercepting scheduleRegisteredReceiver() for "
-                    + Arrays.toString(invocation.getArguments()));
+                    + Arrays.toString(invocation.getArguments()) + " package " + ai.packageName);
             assertHealth();
             final Intent intent = invocation.getArgument(1);
             final Bundle extras = invocation.getArgument(4);
@@ -670,8 +659,15 @@
         }
     }
 
+    /**
+     * Un-pause our handler to process pending events, wait for our queue to go
+     * idle, and then re-pause the handler.
+     */
     private void waitForIdle() throws Exception {
+        mLooper.release();
         mQueue.waitForIdle(LOG_WRITER_INFO);
+        mLooper = Objects.requireNonNull(InstrumentationRegistry.getInstrumentation()
+                .acquireLooperManager(mHandlerThread.getLooper()));
     }
 
     private void verifyScheduleReceiver(ProcessRecord app, Intent intent) throws Exception {
@@ -961,7 +957,7 @@
             } else {
                 // Confirm that app was thawed
                 verify(mAms.mOomAdjuster, atLeastOnce()).unfreezeTemporarily(
-                        eq(receiverApp), eq(OomAdjuster.OOM_ADJ_REASON_START_RECEIVER));
+                        eq(receiverApp), eq(OOM_ADJ_REASON_START_RECEIVER));
 
                 // Confirm that we added package to process
                 verify(receiverApp, atLeastOnce()).addPackage(eq(receiverApp.info.packageName),
@@ -1155,23 +1151,21 @@
         final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
 
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
-        try (SyncBarrier b = new SyncBarrier(mHandlerThread)) {
-            enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, new ArrayList<>(
-                    List.of(makeRegisteredReceiver(receiverApp),
-                            makeManifestReceiver(PACKAGE_GREEN, CLASS_RED),
-                            makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
-                            makeManifestReceiver(PACKAGE_GREEN, CLASS_BLUE)))));
+        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, new ArrayList<>(
+                List.of(makeRegisteredReceiver(receiverApp),
+                        makeManifestReceiver(PACKAGE_GREEN, CLASS_RED),
+                        makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+                        makeManifestReceiver(PACKAGE_GREEN, CLASS_BLUE)))));
 
-            synchronized (mAms) {
-                mQueue.cleanupDisabledPackageReceiversLocked(PACKAGE_GREEN, Set.of(CLASS_GREEN),
-                        UserHandle.USER_SYSTEM);
+        synchronized (mAms) {
+            mQueue.cleanupDisabledPackageReceiversLocked(PACKAGE_GREEN, Set.of(CLASS_GREEN),
+                    UserHandle.USER_SYSTEM);
 
-                // Also try clearing out other unrelated things that should leave
-                // the final receiver intact
-                mQueue.cleanupDisabledPackageReceiversLocked(PACKAGE_RED, null,
-                        UserHandle.USER_SYSTEM);
-                mQueue.cleanupDisabledPackageReceiversLocked(null, null, USER_GUEST);
-            }
+            // Also try clearing out other unrelated things that should leave
+            // the final receiver intact
+            mQueue.cleanupDisabledPackageReceiversLocked(PACKAGE_RED, null,
+                    UserHandle.USER_SYSTEM);
+            mQueue.cleanupDisabledPackageReceiversLocked(null, null, USER_GUEST);
 
             // To maximize test coverage, dump current state; we're not worried
             // about the actual output, just that we don't crash
@@ -1199,21 +1193,19 @@
 
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         final Intent timeZone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
-        try (SyncBarrier b = new SyncBarrier(mHandlerThread)) {
-            enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, USER_GUEST, new ArrayList<>(
-                    List.of(makeRegisteredReceiver(callerApp),
-                            makeManifestReceiver(PACKAGE_GREEN, CLASS_RED, USER_GUEST),
-                            makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN, USER_GUEST),
-                            makeManifestReceiver(PACKAGE_YELLOW, CLASS_BLUE, USER_GUEST)))));
-            enqueueBroadcast(makeBroadcastRecord(timeZone, callerApp, USER_GUEST, new ArrayList<>(
-                    List.of(makeRegisteredReceiver(callerApp),
-                            makeManifestReceiver(PACKAGE_GREEN, CLASS_RED, USER_GUEST),
-                            makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN, USER_GUEST),
-                            makeManifestReceiver(PACKAGE_YELLOW, CLASS_BLUE, USER_GUEST)))));
+        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, USER_GUEST, new ArrayList<>(
+                List.of(makeRegisteredReceiver(callerApp),
+                        makeManifestReceiver(PACKAGE_GREEN, CLASS_RED, USER_GUEST),
+                        makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN, USER_GUEST),
+                        makeManifestReceiver(PACKAGE_YELLOW, CLASS_BLUE, USER_GUEST)))));
+        enqueueBroadcast(makeBroadcastRecord(timeZone, callerApp, USER_GUEST, new ArrayList<>(
+                List.of(makeRegisteredReceiver(callerApp),
+                        makeManifestReceiver(PACKAGE_GREEN, CLASS_RED, USER_GUEST),
+                        makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN, USER_GUEST),
+                        makeManifestReceiver(PACKAGE_YELLOW, CLASS_BLUE, USER_GUEST)))));
 
-            synchronized (mAms) {
-                mQueue.cleanupDisabledPackageReceiversLocked(null, null, USER_GUEST);
-            }
+        synchronized (mAms) {
+            mQueue.cleanupDisabledPackageReceiversLocked(null, null, USER_GUEST);
         }
 
         waitForIdle();
@@ -1239,15 +1231,12 @@
         final ProcessRecord oldApp = makeActiveProcessRecord(PACKAGE_GREEN);
 
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
-        try (SyncBarrier b = new SyncBarrier(mHandlerThread)) {
-            enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, new ArrayList<>(
-                    List.of(makeRegisteredReceiver(oldApp),
-                            makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)))));
-
-            synchronized (mAms) {
-                oldApp.killLocked(TAG, 42, false);
-                mQueue.onApplicationCleanupLocked(oldApp);
-            }
+        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, new ArrayList<>(
+                List.of(makeRegisteredReceiver(oldApp),
+                        makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)))));
+        synchronized (mAms) {
+            oldApp.killLocked(TAG, 42, false);
+            mQueue.onApplicationCleanupLocked(oldApp);
         }
         waitForIdle();
 
@@ -1404,7 +1393,7 @@
 
         // Finally, verify that we thawed the final receiver
         verify(mAms.mOomAdjuster).unfreezeTemporarily(eq(callerApp),
-                eq(OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER));
+                eq(OOM_ADJ_REASON_FINISH_RECEIVER));
     }
 
     /**
@@ -1620,17 +1609,14 @@
         final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         airplane.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        try (SyncBarrier b = new SyncBarrier(mHandlerThread)) {
-            enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
-                    List.of(makeRegisteredReceiver(receiverBlueApp, 10),
-                            makeRegisteredReceiver(receiverGreenApp, 10),
-                            makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
-                            makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW),
-                            makeRegisteredReceiver(receiverYellowApp, -10))));
-            enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
-                    List.of(makeRegisteredReceiver(receiverBlueApp))));
-        }
-
+        enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
+                List.of(makeRegisteredReceiver(receiverBlueApp, 10),
+                        makeRegisteredReceiver(receiverGreenApp, 10),
+                        makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
+                        makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW),
+                        makeRegisteredReceiver(receiverYellowApp, -10))));
+        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+                List.of(makeRegisteredReceiver(receiverBlueApp))));
         waitForIdle();
 
         // Ignore the final foreground broadcast
@@ -1744,17 +1730,15 @@
         final IIntentReceiver resultToFirst = mock(IIntentReceiver.class);
         final IIntentReceiver resultToSecond = mock(IIntentReceiver.class);
 
-        try (SyncBarrier b = new SyncBarrier(mHandlerThread)) {
-            enqueueBroadcast(makeOrderedBroadcastRecord(timezoneFirst, callerApp,
-                    List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
-                            makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN)),
-                    resultToFirst, null));
-            enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
-                    List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_RED))));
-            enqueueBroadcast(makeOrderedBroadcastRecord(timezoneSecond, callerApp,
-                    List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN)),
-                    resultToSecond, null));
-        }
+        enqueueBroadcast(makeOrderedBroadcastRecord(timezoneFirst, callerApp,
+                List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
+                        makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN)),
+                resultToFirst, null));
+        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+                List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_RED))));
+        enqueueBroadcast(makeOrderedBroadcastRecord(timezoneSecond, callerApp,
+                List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN)),
+                resultToSecond, null));
 
         waitForIdle();
         final IApplicationThread blueThread = mAms.getProcessRecordLocked(PACKAGE_BLUE,
@@ -1835,14 +1819,12 @@
         timeTickFirst.putExtra(Intent.EXTRA_INDEX, "third");
         timeTickThird.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
 
-        try (SyncBarrier b = new SyncBarrier(mHandlerThread)) {
-            enqueueBroadcast(makeBroadcastRecord(timeTickFirst, callerApp,
-                    List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE))));
-            enqueueBroadcast(makeBroadcastRecord(timeTickSecond, callerApp,
-                    List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE))));
-            enqueueBroadcast(makeBroadcastRecord(timeTickThird, callerApp,
-                    List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE))));
-        }
+        enqueueBroadcast(makeBroadcastRecord(timeTickFirst, callerApp,
+                List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE))));
+        enqueueBroadcast(makeBroadcastRecord(timeTickSecond, callerApp,
+                List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE))));
+        enqueueBroadcast(makeBroadcastRecord(timeTickThird, callerApp,
+                List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE))));
 
         waitForIdle();
         final IApplicationThread blueThread = mAms.getProcessRecordLocked(PACKAGE_BLUE,
@@ -1877,26 +1859,26 @@
         assertTrue(mQueue.isIdleLocked());
         assertTrue(mQueue.isBeyondBarrierLocked(beforeFirst));
 
-        try (SyncBarrier b = new SyncBarrier(mHandlerThread)) {
-            final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
-            enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
-                    List.of(makeRegisteredReceiver(receiverApp))));
-            afterFirst = SystemClock.uptimeMillis();
+        final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+        enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
+                List.of(makeRegisteredReceiver(receiverApp))));
+        afterFirst = SystemClock.uptimeMillis();
 
-            assertFalse(mQueue.isIdleLocked());
-            assertTrue(mQueue.isBeyondBarrierLocked(beforeFirst));
-            assertFalse(mQueue.isBeyondBarrierLocked(afterFirst));
+        assertFalse(mQueue.isIdleLocked());
+        assertTrue(mQueue.isBeyondBarrierLocked(beforeFirst));
+        assertFalse(mQueue.isBeyondBarrierLocked(afterFirst));
 
-            final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
-            enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
-                    List.of(makeRegisteredReceiver(receiverApp))));
-            afterSecond = SystemClock.uptimeMillis() + 10;
+        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+                List.of(makeRegisteredReceiver(receiverApp))));
+        afterSecond = SystemClock.uptimeMillis() + 10;
 
-            assertFalse(mQueue.isIdleLocked());
-            assertTrue(mQueue.isBeyondBarrierLocked(beforeFirst));
-            assertFalse(mQueue.isBeyondBarrierLocked(afterFirst));
-            assertFalse(mQueue.isBeyondBarrierLocked(afterSecond));
-        }
+        assertFalse(mQueue.isIdleLocked());
+        assertTrue(mQueue.isBeyondBarrierLocked(beforeFirst));
+        assertFalse(mQueue.isBeyondBarrierLocked(afterFirst));
+        assertFalse(mQueue.isBeyondBarrierLocked(afterSecond));
+
+        mLooper.release();
 
         mQueue.waitForBarrier(LOG_WRITER_INFO);
         assertTrue(mQueue.isBeyondBarrierLocked(afterFirst));
@@ -1980,6 +1962,46 @@
     }
 
     /**
+     * Confirm how many times a pathological broadcast pattern results in OOM
+     * adjusts; watches for performance regressions.
+     */
+    @Test
+    public void testOomAdjust_TriggerCount() throws Exception {
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+
+        // Send 8 broadcasts, 4 receivers in the first process,
+        // and 2 alternating in each of the remaining processes
+        synchronized (mAms) {
+            for (int i = 0; i < 8; i++) {
+                final Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+                mQueue.enqueueBroadcastLocked(makeBroadcastRecord(intent, callerApp,
+                        List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+                                makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+                                makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+                                makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+                                makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
+                                makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW),
+                                makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
+                                makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW))));
+            }
+        }
+        waitForIdle();
+
+        final int expectedTimes;
+        switch (mImpl) {
+            // Original stack requested for every single receiver; yikes
+            case DEFAULT: expectedTimes = 64; break;
+            // Modern stack requests once each time we promote a process to
+            // running; we promote "green" twice, and "blue" and "yellow" once
+            case MODERN: expectedTimes = 4; break;
+            default: throw new UnsupportedOperationException();
+        }
+
+        verify(mAms, times(expectedTimes))
+                .updateOomAdjPendingTargetsLocked(eq(OOM_ADJ_REASON_START_RECEIVER));
+    }
+
+    /**
      * Verify that expected events are triggered when a broadcast is finished.
      */
     @Test
@@ -2007,25 +2029,23 @@
         final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
 
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
-        try (SyncBarrier b = new SyncBarrier(mHandlerThread)) {
-            final Object greenReceiver = makeRegisteredReceiver(receiverGreenApp);
-            final Object blueReceiver = makeRegisteredReceiver(receiverBlueApp);
-            final Object yellowReceiver = makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW);
-            final Object orangeReceiver = makeManifestReceiver(PACKAGE_ORANGE, CLASS_ORANGE);
-            enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
-                    List.of(greenReceiver, blueReceiver, yellowReceiver, orangeReceiver)));
+        final Object greenReceiver = makeRegisteredReceiver(receiverGreenApp);
+        final Object blueReceiver = makeRegisteredReceiver(receiverBlueApp);
+        final Object yellowReceiver = makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW);
+        final Object orangeReceiver = makeManifestReceiver(PACKAGE_ORANGE, CLASS_ORANGE);
+        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+                List.of(greenReceiver, blueReceiver, yellowReceiver, orangeReceiver)));
 
-            doAnswer(invocation -> {
-                final BroadcastRecord r = invocation.getArgument(0);
-                final Object o = invocation.getArgument(1);
-                if (airplane.getAction().equals(r.intent.getAction())
-                        && (isReceiverEquals(o, greenReceiver)
-                                || isReceiverEquals(o, orangeReceiver))) {
-                    return "test skipped receiver";
-                }
-                return null;
-            }).when(mSkipPolicy).shouldSkipMessage(any(BroadcastRecord.class), any());
-        }
+        doAnswer(invocation -> {
+            final BroadcastRecord r = invocation.getArgument(0);
+            final Object o = invocation.getArgument(1);
+            if (airplane.getAction().equals(r.intent.getAction())
+                    && (isReceiverEquals(o, greenReceiver)
+                            || isReceiverEquals(o, orangeReceiver))) {
+                return "test skipped receiver";
+            }
+            return null;
+        }).when(mSkipPolicy).shouldSkipMessage(any(BroadcastRecord.class), any());
 
         waitForIdle();
         // Verify that only blue and yellow receiver apps received the broadcast.
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
index 2b6f217..08952ea 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -24,7 +24,12 @@
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_ALL;
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY;
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
-import static com.android.server.am.BroadcastRecord.calculateBlockedUntilTerminalCount;
+import static com.android.server.am.BroadcastRecord.DELIVERY_DEFERRED;
+import static com.android.server.am.BroadcastRecord.DELIVERY_DELIVERED;
+import static com.android.server.am.BroadcastRecord.DELIVERY_PENDING;
+import static com.android.server.am.BroadcastRecord.DELIVERY_SKIPPED;
+import static com.android.server.am.BroadcastRecord.DELIVERY_TIMEOUT;
+import static com.android.server.am.BroadcastRecord.calculateBlockedUntilBeyondCount;
 import static com.android.server.am.BroadcastRecord.calculateDeferUntilActive;
 import static com.android.server.am.BroadcastRecord.calculateUrgent;
 import static com.android.server.am.BroadcastRecord.isReceiverEquals;
@@ -58,7 +63,6 @@
 import com.android.server.am.BroadcastDispatcher.DeferredBootCompletedBroadcastPerUser;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -79,6 +83,7 @@
 @SmallTest
 @RunWith(MockitoJUnitRunner.class)
 public class BroadcastRecordTest {
+    private static final String TAG = "BroadcastRecordTest";
 
     private static final int USER0 = UserHandle.USER_SYSTEM;
     private static final int USER1 = USER0 + 1;
@@ -120,13 +125,13 @@
         assertFalse(isPrioritized(List.of(createResolveInfo(PACKAGE1, getAppId(1), 10))));
 
         assertArrayEquals(new int[] {-1},
-                calculateBlockedUntilTerminalCount(List.of(
+                calculateBlockedUntilBeyondCount(List.of(
                         createResolveInfo(PACKAGE1, getAppId(1), 0)), false));
         assertArrayEquals(new int[] {-1},
-                calculateBlockedUntilTerminalCount(List.of(
+                calculateBlockedUntilBeyondCount(List.of(
                         createResolveInfo(PACKAGE1, getAppId(1), -10)), false));
         assertArrayEquals(new int[] {-1},
-                calculateBlockedUntilTerminalCount(List.of(
+                calculateBlockedUntilBeyondCount(List.of(
                         createResolveInfo(PACKAGE1, getAppId(1), 10)), false));
     }
 
@@ -142,12 +147,12 @@
                 createResolveInfo(PACKAGE3, getAppId(3), 10))));
 
         assertArrayEquals(new int[] {-1,-1,-1},
-                calculateBlockedUntilTerminalCount(List.of(
+                calculateBlockedUntilBeyondCount(List.of(
                         createResolveInfo(PACKAGE1, getAppId(1), 0),
                         createResolveInfo(PACKAGE2, getAppId(2), 0),
                         createResolveInfo(PACKAGE3, getAppId(3), 0)), false));
         assertArrayEquals(new int[] {-1,-1,-1},
-                calculateBlockedUntilTerminalCount(List.of(
+                calculateBlockedUntilBeyondCount(List.of(
                         createResolveInfo(PACKAGE1, getAppId(1), 10),
                         createResolveInfo(PACKAGE2, getAppId(2), 10),
                         createResolveInfo(PACKAGE3, getAppId(3), 10)), false));
@@ -156,26 +161,176 @@
     @Test
     public void testIsPrioritized_Yes() {
         assertTrue(isPrioritized(List.of(
-                createResolveInfo(PACKAGE1, getAppId(1), -10),
+                createResolveInfo(PACKAGE1, getAppId(1), 10),
                 createResolveInfo(PACKAGE2, getAppId(2), 0),
-                createResolveInfo(PACKAGE3, getAppId(3), 10))));
+                createResolveInfo(PACKAGE3, getAppId(3), -10))));
         assertTrue(isPrioritized(List.of(
-                createResolveInfo(PACKAGE1, getAppId(1), 0),
+                createResolveInfo(PACKAGE1, getAppId(1), 10),
                 createResolveInfo(PACKAGE2, getAppId(2), 0),
-                createResolveInfo(PACKAGE3, getAppId(3), 10))));
+                createResolveInfo(PACKAGE3, getAppId(3), 0))));
 
         assertArrayEquals(new int[] {0,1,2},
-                calculateBlockedUntilTerminalCount(List.of(
-                        createResolveInfo(PACKAGE1, getAppId(1), -10),
+                calculateBlockedUntilBeyondCount(List.of(
+                        createResolveInfo(PACKAGE1, getAppId(1), 10),
                         createResolveInfo(PACKAGE2, getAppId(2), 0),
-                        createResolveInfo(PACKAGE3, getAppId(3), 10)), false));
+                        createResolveInfo(PACKAGE3, getAppId(3), -10)), false));
         assertArrayEquals(new int[] {0,0,2,3,3},
-                calculateBlockedUntilTerminalCount(List.of(
-                        createResolveInfo(PACKAGE1, getAppId(1), 0),
-                        createResolveInfo(PACKAGE2, getAppId(2), 0),
+                calculateBlockedUntilBeyondCount(List.of(
+                        createResolveInfo(PACKAGE1, getAppId(1), 20),
+                        createResolveInfo(PACKAGE2, getAppId(2), 20),
                         createResolveInfo(PACKAGE3, getAppId(3), 10),
-                        createResolveInfo(PACKAGE3, getAppId(3), 20),
-                        createResolveInfo(PACKAGE3, getAppId(3), 20)), false));
+                        createResolveInfo(PACKAGE3, getAppId(3), 0),
+                        createResolveInfo(PACKAGE3, getAppId(3), 0)), false));
+    }
+
+    @Test
+    public void testSetDeliveryState_Single() {
+        final BroadcastRecord r = createBroadcastRecord(
+                new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of(
+                        createResolveInfoWithPriority(0)));
+        assertEquals(DELIVERY_PENDING, r.getDeliveryState(0));
+        assertBlocked(r, false);
+        assertTerminalDeferredBeyond(r, 0, 0, 0);
+
+        r.setDeliveryState(0, DELIVERY_DEFERRED, TAG);
+        assertEquals(DELIVERY_DEFERRED, r.getDeliveryState(0));
+        assertBlocked(r, false);
+        assertTerminalDeferredBeyond(r, 0, 1, 1);
+
+        // Identical state change has no effect
+        r.setDeliveryState(0, DELIVERY_DEFERRED, TAG);
+        assertEquals(DELIVERY_DEFERRED, r.getDeliveryState(0));
+        assertBlocked(r, false);
+        assertTerminalDeferredBeyond(r, 0, 1, 1);
+
+        // Moving to terminal state updates counters
+        r.setDeliveryState(0, DELIVERY_DELIVERED, TAG);
+        assertEquals(DELIVERY_DELIVERED, r.getDeliveryState(0));
+        assertBlocked(r, false);
+        assertTerminalDeferredBeyond(r, 1, 0, 1);
+
+        // Trying to change terminal state has no effect
+        r.setDeliveryState(0, DELIVERY_TIMEOUT, TAG);
+        assertEquals(DELIVERY_DELIVERED, r.getDeliveryState(0));
+        assertBlocked(r, false);
+        assertTerminalDeferredBeyond(r, 1, 0, 1);
+    }
+
+    @Test
+    public void testSetDeliveryState_Unordered() {
+        final BroadcastRecord r = createBroadcastRecord(
+                new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of(
+                        createResolveInfoWithPriority(0),
+                        createResolveInfoWithPriority(0),
+                        createResolveInfoWithPriority(0)));
+        assertBlocked(r, false, false, false);
+        assertTerminalDeferredBeyond(r, 0, 0, 0);
+
+        // Even though we finish a middle item in the tranche, we're not
+        // "beyond" it because there is still unfinished work before it
+        r.setDeliveryState(1, DELIVERY_DELIVERED, TAG);
+        assertBlocked(r, false, false, false);
+        assertTerminalDeferredBeyond(r, 1, 0, 0);
+
+        r.setDeliveryState(0, DELIVERY_DELIVERED, TAG);
+        assertBlocked(r, false, false, false);
+        assertTerminalDeferredBeyond(r, 2, 0, 2);
+
+        r.setDeliveryState(2, DELIVERY_DELIVERED, TAG);
+        assertBlocked(r, false, false, false);
+        assertTerminalDeferredBeyond(r, 3, 0, 3);
+    }
+
+    @Test
+    public void testSetDeliveryState_Ordered() {
+        final BroadcastRecord r = createOrderedBroadcastRecord(
+                new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of(
+                        createResolveInfoWithPriority(0),
+                        createResolveInfoWithPriority(0),
+                        createResolveInfoWithPriority(0)));
+        assertBlocked(r, false, true, true);
+        assertTerminalDeferredBeyond(r, 0, 0, 0);
+
+        r.setDeliveryState(0, DELIVERY_DELIVERED, TAG);
+        assertBlocked(r, false, false, true);
+        assertTerminalDeferredBeyond(r, 1, 0, 1);
+
+        r.setDeliveryState(1, DELIVERY_DELIVERED, TAG);
+        assertBlocked(r, false, false, false);
+        assertTerminalDeferredBeyond(r, 2, 0, 2);
+
+        r.setDeliveryState(2, DELIVERY_DELIVERED, TAG);
+        assertBlocked(r, false, false, false);
+        assertTerminalDeferredBeyond(r, 3, 0, 3);
+    }
+
+    @Test
+    public void testSetDeliveryState_DeferUntilActive() {
+        final BroadcastRecord r = createBroadcastRecord(
+                new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of(
+                        createResolveInfoWithPriority(10),
+                        createResolveInfoWithPriority(10),
+                        createResolveInfoWithPriority(10),
+                        createResolveInfoWithPriority(0),
+                        createResolveInfoWithPriority(0),
+                        createResolveInfoWithPriority(0),
+                        createResolveInfoWithPriority(-10),
+                        createResolveInfoWithPriority(-10),
+                        createResolveInfoWithPriority(-10)));
+        assertBlocked(r, false, false, false, true, true, true, true, true, true);
+        assertTerminalDeferredBeyond(r, 0, 0, 0);
+
+        r.setDeliveryState(0, DELIVERY_PENDING, TAG);
+        r.setDeliveryState(1, DELIVERY_DEFERRED, TAG);
+        r.setDeliveryState(2, DELIVERY_PENDING, TAG);
+        r.setDeliveryState(3, DELIVERY_DEFERRED, TAG);
+        r.setDeliveryState(4, DELIVERY_DEFERRED, TAG);
+        r.setDeliveryState(5, DELIVERY_DEFERRED, TAG);
+        r.setDeliveryState(6, DELIVERY_DEFERRED, TAG);
+        r.setDeliveryState(7, DELIVERY_PENDING, TAG);
+        r.setDeliveryState(8, DELIVERY_DEFERRED, TAG);
+
+        // Verify deferred counts ratchet up, but we're not "beyond" the first
+        // still-pending receiver
+        assertBlocked(r, false, false, false, true, true, true, true, true, true);
+        assertTerminalDeferredBeyond(r, 0, 6, 0);
+
+        // We're still not "beyond" the first still-pending receiver, even when
+        // we finish a receiver later in the first tranche
+        r.setDeliveryState(2, DELIVERY_DELIVERED, TAG);
+        assertBlocked(r, false, false, false, true, true, true, true, true, true);
+        assertTerminalDeferredBeyond(r, 1, 6, 0);
+
+        // Completing that last item in first tranche means we now unblock the
+        // second tranche, and since it's entirely deferred, the third traunche
+        // is unblocked too
+        r.setDeliveryState(0, DELIVERY_DELIVERED, TAG);
+        assertBlocked(r, false, false, false, false, false, false, false, false, false);
+        assertTerminalDeferredBeyond(r, 2, 6, 7);
+
+        // Moving a deferred item in an earlier tranche back to being pending
+        // doesn't change the fact that we've already moved beyond it
+        r.setDeliveryState(1, DELIVERY_PENDING, TAG);
+        assertBlocked(r, false, false, false, false, false, false, false, false, false);
+        assertTerminalDeferredBeyond(r, 2, 5, 7);
+        r.setDeliveryState(1, DELIVERY_DELIVERED, TAG);
+        assertBlocked(r, false, false, false, false, false, false, false, false, false);
+        assertTerminalDeferredBeyond(r, 3, 5, 7);
+
+        // Completing middle pending item is enough to fast-forward to end
+        r.setDeliveryState(7, DELIVERY_DELIVERED, TAG);
+        assertBlocked(r, false, false, false, false, false, false, false, false, false);
+        assertTerminalDeferredBeyond(r, 4, 5, 9);
+
+        // Moving everyone else directly into a finished state updates all the
+        // terminal counters
+        r.setDeliveryState(3, DELIVERY_SKIPPED, TAG);
+        r.setDeliveryState(4, DELIVERY_SKIPPED, TAG);
+        r.setDeliveryState(5, DELIVERY_SKIPPED, TAG);
+        r.setDeliveryState(6, DELIVERY_SKIPPED, TAG);
+        r.setDeliveryState(8, DELIVERY_SKIPPED, TAG);
+        assertBlocked(r, false, false, false, false, false, false, false, false, false);
+        assertTerminalDeferredBeyond(r, 9, 0, 9);
     }
 
     @Test
@@ -688,6 +843,10 @@
                 : errorMsg.insert(0, "Contains unexpected receiver: ").toString();
     }
 
+    private static ResolveInfo createResolveInfoWithPriority(int priority) {
+        return createResolveInfo(PACKAGE1, getAppId(1), priority);
+    }
+
     private static ResolveInfo createResolveInfo(String packageName, int uid) {
         return createResolveInfo(packageName, uid, 0);
     }
@@ -738,21 +897,40 @@
         return excludedList;
     }
 
+    private BroadcastRecord createBroadcastRecord(Intent intent,
+            List<ResolveInfo> receivers) {
+        return createBroadcastRecord(receivers, USER0, intent, null /* filterExtrasForReceiver */,
+                null /* options */, false);
+    }
+
+    private BroadcastRecord createOrderedBroadcastRecord(Intent intent,
+            List<ResolveInfo> receivers) {
+        return createBroadcastRecord(receivers, USER0, intent, null /* filterExtrasForReceiver */,
+                null /* options */, true);
+    }
+
     private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
             Intent intent) {
         return createBroadcastRecord(receivers, userId, intent, null /* filterExtrasForReceiver */,
-                null /* options */);
+                null /* options */, false);
     }
 
     private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
             Intent intent, BroadcastOptions options) {
         return createBroadcastRecord(receivers, userId, intent, null /* filterExtrasForReceiver */,
-                options);
+                options, false);
     }
 
     private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
             Intent intent, BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
             BroadcastOptions options) {
+        return createBroadcastRecord(receivers, userId, intent, filterExtrasForReceiver,
+                options, false);
+    }
+
+    private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
+            Intent intent, BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+            BroadcastOptions options, boolean ordered) {
         return new BroadcastRecord(
                 mQueue /* queue */,
                 intent,
@@ -774,7 +952,7 @@
                 0 /* resultCode */,
                 null /* resultData */,
                 null /* resultExtras */,
-                false /* serialized */,
+                ordered /* serialized */,
                 false /* sticky */,
                 false /* initialSticky */,
                 userId,
@@ -789,6 +967,20 @@
 
     private static boolean isPrioritized(List<Object> receivers) {
         return BroadcastRecord.isPrioritized(
-                calculateBlockedUntilTerminalCount(receivers, false), false);
+                calculateBlockedUntilBeyondCount(receivers, false), false);
+    }
+
+    private static void assertBlocked(BroadcastRecord r, boolean... blocked) {
+        assertEquals(r.receivers.size(), blocked.length);
+        for (int i = 0; i < blocked.length; i++) {
+            assertEquals("blocked " + i, blocked[i], r.isBlocked(i));
+        }
+    }
+
+    private static void assertTerminalDeferredBeyond(BroadcastRecord r,
+            int expectedTerminalCount, int expectedDeferredCount, int expectedBeyondCount) {
+        assertEquals("terminal", expectedTerminalCount, r.terminalCount);
+        assertEquals("deferred", expectedDeferredCount, r.deferredCount);
+        assertEquals("beyond", expectedBeyondCount, r.beyondCount);
     }
 }
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 485ce33..cda5456 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -38,11 +38,12 @@
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
 import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
 import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
-import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_ACTIVITY;
 import static com.android.server.am.ProcessList.BACKUP_APP_ADJ;
 import static com.android.server.am.ProcessList.CACHED_APP_MAX_ADJ;
 import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ;
@@ -254,12 +255,13 @@
      * - If there's only one process, then it calls updateOomAdjLocked(ProcessRecord, int).
      * - Otherwise, sets the processes to the LRU and run updateOomAdjLocked(int).
      */
+    @SuppressWarnings("GuardedBy")
     private void updateOomAdj(ProcessRecord... apps) {
         if (apps.length == 1) {
-            sService.mOomAdjuster.updateOomAdjLocked(apps[0], OomAdjuster.OOM_ADJ_REASON_NONE);
+            sService.mOomAdjuster.updateOomAdjLocked(apps[0], OOM_ADJ_REASON_NONE);
         } else {
             setProcessesToLru(apps);
-            sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+            sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
             sService.mProcessList.getLruProcessesLOSP().clear();
         }
     }
@@ -658,7 +660,7 @@
         ServiceRecord s = bindService(app, system,
                 null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class));
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND,
                 PERCEPTIBLE_APP_ADJ + 1, SCHED_GROUP_DEFAULT);
@@ -1226,7 +1228,7 @@
                     mock(IBinder.class));
             client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-            sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+            sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
 
             assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj());
         }
@@ -1243,7 +1245,7 @@
                     mock(IBinder.class));
             client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-            sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+            sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
             doReturn(false).when(wpc).isHeavyWeightProcess();
 
             assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj());
@@ -1497,7 +1499,7 @@
 
         client2.mServices.setHasForegroundServices(false, 0, /* hasNoneType=*/false);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(client2, OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(client2, OOM_ADJ_REASON_NONE);
 
         assertEquals(PROCESS_STATE_CACHED_EMPTY, client2.mState.getSetProcState());
         assertEquals(PROCESS_STATE_CACHED_EMPTY, client.mState.getSetProcState());
@@ -1919,7 +1921,7 @@
         doReturn(client2).when(sService).getTopApp();
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
 
-        sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(app2, OOM_ADJ_REASON_NONE);
         assertProcStates(app2, PROCESS_STATE_BOUND_TOP, VISIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
     }
@@ -2029,7 +2031,7 @@
             setServiceMap(s3, MOCKAPP5_UID, cn3);
             setServiceMap(c2s, MOCKAPP3_UID, cn4);
             app2UidRecord.setIdle(false);
-            sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+            sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
 
             assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
                     SCHED_GROUP_DEFAULT);
@@ -2055,7 +2057,7 @@
                     anyInt(), anyBoolean(), anyBoolean(), anyBoolean());
             doNothing().when(sService.mServices)
                     .scheduleServiceTimeoutLocked(any(ProcessRecord.class));
-            sService.mOomAdjuster.updateOomAdjLocked(client1, OomAdjuster.OOM_ADJ_REASON_NONE);
+            sService.mOomAdjuster.updateOomAdjLocked(client1, OOM_ADJ_REASON_NONE);
 
             assertEquals(PROCESS_STATE_CACHED_EMPTY, client1.mState.getSetProcState());
             assertEquals(PROCESS_STATE_SERVICE, app1.mState.getSetProcState());
@@ -2427,7 +2429,7 @@
         app2.mState.setHasShownUi(false);
 
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-ui-services");
         assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj2, "cch-started-services");
@@ -2436,7 +2438,7 @@
         app.mState.setAdjType(null);
         app.mState.setSetAdj(UNKNOWN_ADJ);
         app.mState.setHasShownUi(false);
-        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
 
@@ -2445,7 +2447,7 @@
         app.mState.setAdjType(null);
         app.mState.setSetAdj(UNKNOWN_ADJ);
         s.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1;
-        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
 
@@ -2463,7 +2465,7 @@
         s.lastActivity = now;
 
         app.mServices.startService(s);
-        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
         assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
@@ -2474,7 +2476,7 @@
         app.mState.setSetAdj(UNKNOWN_ADJ);
         app.mState.setHasShownUi(false);
         s.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1;
-        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
         assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
@@ -2482,7 +2484,7 @@
         doReturn(userOther).when(sService.mUserController).getCurrentUserId();
         sService.mOomAdjuster.handleUserSwitchedLocked();
 
-        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
         assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
         assertProcStates(app2, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/BackupAndRestoreFeatureFlagsTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/BackupAndRestoreFeatureFlagsTest.java
index 201da35..9f685b4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/BackupAndRestoreFeatureFlagsTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/BackupAndRestoreFeatureFlagsTest.java
@@ -104,4 +104,24 @@
         assertThat(BackupAndRestoreFeatureFlags.getFullBackupUtilsRouteBufferSizeBytes())
                 .isEqualTo(5678);
     }
+
+    @Test
+    public void getUnifiedRestoreContinueAfterTransportFailureInKvRestore_notSet_returnsDefault() {
+        assertThat(
+                BackupAndRestoreFeatureFlags
+                        .getUnifiedRestoreContinueAfterTransportFailureInKvRestore())
+                .isEqualTo(true);
+    }
+
+    @Test
+    public void getUnifiedRestoreContinueAfterTransportFailureInKvRestore_set_returnsSetValue() {
+        DeviceConfig.setProperty(/*namespace=*/ "backup_and_restore",
+                /*name=*/ "unified_restore_continue_after_transport_failure_in_kv_restore",
+                /*value=*/ "false", /*makeDefault=*/ false);
+
+        assertThat(
+                BackupAndRestoreFeatureFlags
+                        .getUnifiedRestoreContinueAfterTransportFailureInKvRestore())
+                .isEqualTo(false);
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
index 017c939..c84797f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
@@ -25,20 +25,33 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.when;
 
+import android.app.backup.BackupDataInput;
+import android.app.backup.BackupDataOutput;
+import android.app.backup.BackupTransport;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.os.Message;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.provider.DeviceConfig;
+
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import android.app.backup.BackupDataInput;
-import android.app.backup.BackupDataOutput;
-import android.platform.test.annotations.Presubmit;
-
+import com.android.modules.utils.testing.TestableDeviceConfig;
 import com.android.server.backup.UserBackupManagerService;
+import com.android.server.backup.internal.BackupHandler;
+import com.android.server.backup.transport.BackupTransportClient;
+import com.android.server.backup.transport.TransportConnection;
+import com.android.server.backup.transport.TransportNotAvailableException;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
@@ -62,9 +75,14 @@
     private static final String SYSTEM_PACKAGE_NAME = "android";
     private static final String NON_SYSTEM_PACKAGE_NAME = "package";
 
-    @Mock private BackupDataInput mBackupDataInput;
-    @Mock private BackupDataOutput mBackupDataOutput;
-    @Mock private UserBackupManagerService mBackupManagerService;
+    @Mock
+    private BackupDataInput mBackupDataInput;
+    @Mock
+    private BackupDataOutput mBackupDataOutput;
+    @Mock
+    private UserBackupManagerService mBackupManagerService;
+    @Mock
+    private TransportConnection mTransportConnection;
 
     private Set<String> mExcludedkeys = new HashSet<>();
     private Map<String, String> mBackupData = new HashMap<>();
@@ -74,12 +92,20 @@
     private Set<String> mBackupDataDump;
     private PerformUnifiedRestoreTask mRestoreTask;
 
+    @Rule
+    public TestableDeviceConfig.TestableDeviceConfigRule
+            mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
+
+    private Context mContext;
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
         populateTestData();
 
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
         mBackupDataSource = new ArrayDeque<>(mBackupData.keySet());
         when(mBackupDataInput.readNextHeader()).then(new Answer<Boolean>() {
             @Override
@@ -106,7 +132,7 @@
                     }
                 });
 
-        mRestoreTask = new PerformUnifiedRestoreTask(mBackupManagerService);
+        mRestoreTask = new PerformUnifiedRestoreTask(mBackupManagerService, mTransportConnection);
     }
 
     private void populateTestData() {
@@ -179,4 +205,64 @@
 
         assertTrue(mRestoreTask.shouldStageBackupData(SYSTEM_PACKAGE_NAME));
     }
+
+    @Test
+    public void testFailedKeyValueRestore_continueAfterFeatureEnabled_nextStateIsRunningQueue()
+            throws TransportNotAvailableException, RemoteException {
+        DeviceConfig.setProperty(
+                "backup_and_restore",
+                "unified_restore_continue_after_transport_failure_in_kv_restore",
+                "true",
+                false);
+
+        setupForRestoreKeyValueState(BackupTransport.TRANSPORT_ERROR);
+
+        mRestoreTask.setCurrentUnifiedRestoreStateForTesting(UnifiedRestoreState.RESTORE_KEYVALUE);
+        mRestoreTask.setStateDirForTesting(mContext.getCacheDir());
+
+        PackageInfo testPackageInfo = new PackageInfo();
+        testPackageInfo.packageName = "test.package.name";
+        mRestoreTask.initiateOneRestoreForTesting(testPackageInfo, 0L);
+        assertTrue(
+                mRestoreTask.getCurrentUnifiedRestoreStateForTesting()
+                        == UnifiedRestoreState.RUNNING_QUEUE);
+    }
+
+    @Test
+    public void testFailedKeyValueRestore_continueAfterFeatureDisabled_nextStateIsFinal()
+            throws RemoteException, TransportNotAvailableException {
+        DeviceConfig.setProperty(
+                "backup_and_restore",
+                "unified_restore_continue_after_transport_failure_in_kv_restore",
+                "false",
+                false);
+
+        setupForRestoreKeyValueState(BackupTransport.TRANSPORT_ERROR);
+
+        mRestoreTask.setCurrentUnifiedRestoreStateForTesting(UnifiedRestoreState.RESTORE_KEYVALUE);
+        mRestoreTask.setStateDirForTesting(mContext.getCacheDir());
+
+        PackageInfo testPackageInfo = new PackageInfo();
+        testPackageInfo.packageName = "test.package.name";
+        mRestoreTask.initiateOneRestoreForTesting(testPackageInfo, 0L);
+        assertTrue(
+                mRestoreTask.getCurrentUnifiedRestoreStateForTesting()
+                        == UnifiedRestoreState.FINAL);
+    }
+
+    private void setupForRestoreKeyValueState(int transportStatus)
+            throws RemoteException, TransportNotAvailableException {
+        // Mock BackupHandler to do nothing when executeNextState() is called
+        BackupHandler backupHandler = Mockito.mock(BackupHandler.class);
+        when(backupHandler.obtainMessage(anyInt(), any())).thenReturn(new Message());
+        when(backupHandler.sendMessage(any())).thenReturn(true);
+
+        // Return cache directory for any bookkeeping or maintaining persistent state.
+        when(mBackupManagerService.getDataDir()).thenReturn(mContext.getCacheDir());
+        when(mBackupManagerService.getBackupHandler()).thenReturn(backupHandler);
+
+        BackupTransportClient transport = Mockito.mock(BackupTransportClient.class);
+        when(transport.getRestoreData(any())).thenReturn(transportStatus);
+        when(mTransportConnection.connectOrThrow(any())).thenReturn(transport);
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
index 45fefe4..ca857f1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -38,9 +38,7 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.when;
 
-import android.content.ContentResolver;
 import android.content.Context;
-import android.content.ContextWrapper;
 import android.content.res.Resources;
 import android.hardware.Sensor;
 import android.hardware.SensorEventListener;
@@ -51,18 +49,18 @@
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.SystemProperties;
-import android.os.UserHandle;
 import android.os.test.TestLooper;
 import android.provider.Settings;
+import android.testing.TestableContext;
 import android.util.FloatProperty;
 import android.view.Display;
 import android.view.DisplayInfo;
 
-import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.server.ExtendedMockitoRule;
 import com.android.server.LocalServices;
 import com.android.server.am.BatteryStatsService;
 import com.android.server.display.RampAnimator.DualRampAnimator;
@@ -74,12 +72,12 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
-import org.mockito.MockitoSession;
 import org.mockito.quality.Strictness;
 import org.mockito.stubbing.Answer;
 
@@ -97,11 +95,9 @@
     private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
     private static final float PROX_SENSOR_MAX_RANGE = 5;
 
-    private MockitoSession mSession;
     private OffsettableClock mClock;
     private TestLooper mTestLooper;
     private Handler mHandler;
-    private Context mContextSpy;
     private DisplayPowerControllerHolder mHolder;
     private Sensor mProxSensor;
 
@@ -118,40 +114,44 @@
     @Mock
     private PowerManager mPowerManagerMock;
     @Mock
-    private Resources mResourcesMock;
-    @Mock
     private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock;
 
     @Captor
     private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
 
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getContext());
+
+    @Rule
+    public final ExtendedMockitoRule mExtendedMockitoRule =
+            new ExtendedMockitoRule.Builder(this)
+                    .setStrictness(Strictness.LENIENT)
+                    .spyStatic(SystemProperties.class)
+                    .spyStatic(BatteryStatsService.class)
+                    .build();
+
     @Before
     public void setUp() throws Exception {
-        mSession = ExtendedMockito.mockitoSession()
-                .initMocks(this)
-                .strictness(Strictness.LENIENT)
-                .spyStatic(SystemProperties.class)
-                .spyStatic(LocalServices.class)
-                .spyStatic(BatteryStatsService.class)
-                .spyStatic(Settings.System.class)
-                .startMocking();
-        mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
         mClock = new OffsettableClock.Stopped();
         mTestLooper = new TestLooper(mClock::now);
         mHandler = new Handler(mTestLooper.getLooper());
-        addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
 
-        when(mContextSpy.getSystemService(eq(PowerManager.class))).thenReturn(mPowerManagerMock);
-        when(mContextSpy.getResources()).thenReturn(mResourcesMock);
+        // Put the system into manual brightness by default, just to minimize unexpected events and
+        // have a consistent starting state
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+
+        addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
+        addLocalServiceMock(ColorDisplayService.ColorDisplayServiceInternal.class,
+                mCdsiMock);
+
+        mContext.addMockSystemService(PowerManager.class, mPowerManagerMock);
 
         doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
                 SystemProperties.set(anyString(), any()));
-        doAnswer((Answer<ColorDisplayService.ColorDisplayServiceInternal>) invocationOnMock ->
-                mCdsiMock).when(() -> LocalServices.getService(
-                ColorDisplayService.ColorDisplayServiceInternal.class));
         doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService);
-        doAnswer((Answer<Boolean>) invocationOnMock -> true).when(() ->
-                Settings.System.putFloatForUser(any(), any(), anyFloat(), anyInt()));
 
         setUpSensors();
         mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
@@ -159,8 +159,8 @@
 
     @After
     public void tearDown() {
-        mSession.finishMocking();
         LocalServices.removeServiceForTest(WindowManagerPolicy.class);
+        LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
     }
 
     @Test
@@ -421,11 +421,9 @@
 
     @Test
     public void testDisplayBrightnessFollowers_AutomaticBrightness() {
-        doAnswer((Answer<Integer>) invocationOnMock ->
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
-                .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
-                        eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
-                        eq(UserHandle.USER_CURRENT)));
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
         final float brightness = 0.4f;
         final float nits = 300;
         final float ambientLux = 3000;
@@ -436,7 +434,7 @@
         when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
         when(mHolder.automaticBrightnessController.getAmbientLux()).thenReturn(ambientLux);
         when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        DisplayPowerController followerDpc = mock(DisplayPowerController.class);
+        DisplayPowerController2 followerDpc = mock(DisplayPowerController2.class);
 
         mHolder.dpc.addDisplayBrightnessFollower(followerDpc);
         DisplayPowerRequest dpr = new DisplayPowerRequest();
@@ -542,11 +540,9 @@
 
     @Test
     public void testSetScreenOffBrightnessSensorEnabled_DisplayIsOff() {
-        doAnswer((Answer<Integer>) invocationOnMock ->
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
-                .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
-                        eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
-                        eq(UserHandle.USER_CURRENT)));
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
 
         DisplayPowerRequest dpr = new DisplayPowerRequest();
         dpr.policy = DisplayPowerRequest.POLICY_OFF;
@@ -577,17 +573,14 @@
 
     @Test
     public void testSetScreenOffBrightnessSensorEnabled_DisplayIsInDoze() {
-        doAnswer((Answer<Integer>) invocationOnMock ->
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
-                .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
-                        eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
-                        eq(UserHandle.USER_CURRENT)));
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
 
         DisplayPowerRequest dpr = new DisplayPowerRequest();
         dpr.policy = DisplayPowerRequest.POLICY_DOZE;
-        when(mResourcesMock.getBoolean(
-                com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing))
-                .thenReturn(true);
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true);
         mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
         advanceTime(1); // Run updatePowerState
 
@@ -615,12 +608,7 @@
 
     @Test
     public void testSetScreenOffBrightnessSensorDisabled_AutoBrightnessIsDisabled() {
-        doAnswer((Answer<Integer>) invocationOnMock ->
-                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL)
-                .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
-                        eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
-                        eq(UserHandle.USER_CURRENT)));
-
+        // Tests are set up with manual brightness by default, so no need to set it here.
         DisplayPowerRequest dpr = new DisplayPowerRequest();
         dpr.policy = DisplayPowerRequest.POLICY_OFF;
         mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
@@ -632,11 +620,9 @@
 
     @Test
     public void testSetScreenOffBrightnessSensorDisabled_DisplayIsDisabled() {
-        doAnswer((Answer<Integer>) invocationOnMock ->
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
-                .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
-                        eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
-                        eq(UserHandle.USER_CURRENT)));
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
         mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ false);
 
         DisplayPowerRequest dpr = new DisplayPowerRequest();
@@ -690,9 +676,9 @@
     public void testBrightnessNitsPersistWhenDisplayDeviceChanges() {
         float brightness = 0.3f;
         float nits = 500;
-        when(mResourcesMock.getBoolean(
-                com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay))
-                .thenReturn(true);
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay,
+                true);
         mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
         when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
 
@@ -753,7 +739,7 @@
                 any(HysteresisLevels.class),
                 any(HysteresisLevels.class),
                 any(HysteresisLevels.class),
-                eq(mContextSpy),
+                eq(mContext),
                 any(HighBrightnessModeController.class),
                 any(BrightnessThrottler.class),
                 isNull(),
@@ -862,7 +848,7 @@
         setUpDisplay(displayId, uniqueId, display, device, config, isEnabled);
 
         final DisplayPowerController2 dpc = new DisplayPowerController2(
-                mContextSpy, injector, mDisplayPowerCallbacksMock, mHandler,
+                mContext, injector, mDisplayPowerCallbacksMock, mHandler,
                 mSensorManagerMock, mDisplayBlankerMock, display,
                 mBrightnessTrackerMock, brightnessSetting, () -> {},
                 hbmMetadata, /* bootCompleted= */ false);
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
index d9133a4..0b97c5c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -38,9 +38,7 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.when;
 
-import android.content.ContentResolver;
 import android.content.Context;
-import android.content.ContextWrapper;
 import android.content.res.Resources;
 import android.hardware.Sensor;
 import android.hardware.SensorEventListener;
@@ -51,18 +49,18 @@
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.SystemProperties;
-import android.os.UserHandle;
 import android.os.test.TestLooper;
 import android.provider.Settings;
+import android.testing.TestableContext;
 import android.util.FloatProperty;
 import android.view.Display;
 import android.view.DisplayInfo;
 
-import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.server.ExtendedMockitoRule;
 import com.android.server.LocalServices;
 import com.android.server.am.BatteryStatsService;
 import com.android.server.display.RampAnimator.DualRampAnimator;
@@ -75,12 +73,12 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
-import org.mockito.MockitoSession;
 import org.mockito.quality.Strictness;
 import org.mockito.stubbing.Answer;
 
@@ -98,11 +96,9 @@
     private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
     private static final float PROX_SENSOR_MAX_RANGE = 5;
 
-    private MockitoSession mSession;
     private OffsettableClock mClock;
     private TestLooper mTestLooper;
     private Handler mHandler;
-    private Context mContextSpy;
     private DisplayPowerControllerHolder mHolder;
     private Sensor mProxSensor;
 
@@ -119,41 +115,45 @@
     @Mock
     private PowerManager mPowerManagerMock;
     @Mock
-    private Resources mResourcesMock;
-    @Mock
     private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock;
 
     @Captor
     private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
 
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getContext());
+
+    @Rule
+    public final ExtendedMockitoRule mExtendedMockitoRule =
+            new ExtendedMockitoRule.Builder(this)
+                    .setStrictness(Strictness.LENIENT)
+                    .spyStatic(SystemProperties.class)
+                    .spyStatic(BatteryStatsService.class)
+                    .build();
+
     @Before
     public void setUp() throws Exception {
-        mSession = ExtendedMockito.mockitoSession()
-                .initMocks(this)
-                .strictness(Strictness.LENIENT)
-                .spyStatic(SystemProperties.class)
-                .spyStatic(LocalServices.class)
-                .spyStatic(BatteryStatsService.class)
-                .spyStatic(Settings.System.class)
-                .startMocking();
-        mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
         mClock = new OffsettableClock.Stopped();
         mTestLooper = new TestLooper(mClock::now);
         mHandler = new Handler(mTestLooper.getLooper());
 
-        addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
+        // Put the system into manual brightness by default, just to minimize unexpected events and
+        // have a consistent starting state
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
 
-        when(mContextSpy.getSystemService(eq(PowerManager.class))).thenReturn(mPowerManagerMock);
-        when(mContextSpy.getResources()).thenReturn(mResourcesMock);
+
+        addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
+        addLocalServiceMock(ColorDisplayService.ColorDisplayServiceInternal.class,
+                mCdsiMock);
+
+        mContext.addMockSystemService(PowerManager.class, mPowerManagerMock);
 
         doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
                 SystemProperties.set(anyString(), any()));
-        doAnswer((Answer<ColorDisplayService.ColorDisplayServiceInternal>) invocationOnMock ->
-                mCdsiMock).when(() -> LocalServices.getService(
-                ColorDisplayService.ColorDisplayServiceInternal.class));
         doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService);
-        doAnswer((Answer<Boolean>) invocationOnMock -> true).when(() ->
-                Settings.System.putFloatForUser(any(), any(), anyFloat(), anyInt()));
 
         setUpSensors();
         mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
@@ -161,8 +161,8 @@
 
     @After
     public void tearDown() {
-        mSession.finishMocking();
         LocalServices.removeServiceForTest(WindowManagerPolicy.class);
+        LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
     }
 
     @Test
@@ -425,11 +425,9 @@
 
     @Test
     public void testDisplayBrightnessFollowers_AutomaticBrightness() {
-        doAnswer((Answer<Integer>) invocationOnMock ->
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
-                .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
-                        eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
-                        eq(UserHandle.USER_CURRENT)));
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
         final float brightness = 0.4f;
         final float nits = 300;
         final float ambientLux = 3000;
@@ -547,11 +545,9 @@
 
     @Test
     public void testSetScreenOffBrightnessSensorEnabled_DisplayIsOff() {
-        doAnswer((Answer<Integer>) invocationOnMock ->
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
-                .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
-                        eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
-                        eq(UserHandle.USER_CURRENT)));
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
 
         DisplayPowerRequest dpr = new DisplayPowerRequest();
         dpr.policy = DisplayPowerRequest.POLICY_OFF;
@@ -582,17 +578,14 @@
 
     @Test
     public void testSetScreenOffBrightnessSensorEnabled_DisplayIsInDoze() {
-        doAnswer((Answer<Integer>) invocationOnMock ->
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
-                .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
-                        eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
-                        eq(UserHandle.USER_CURRENT)));
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
 
         DisplayPowerRequest dpr = new DisplayPowerRequest();
         dpr.policy = DisplayPowerRequest.POLICY_DOZE;
-        when(mResourcesMock.getBoolean(
-                com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing))
-                .thenReturn(true);
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true);
         mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
         advanceTime(1); // Run updatePowerState
 
@@ -620,12 +613,7 @@
 
     @Test
     public void testSetScreenOffBrightnessSensorDisabled_AutoBrightnessIsDisabled() {
-        doAnswer((Answer<Integer>) invocationOnMock ->
-                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL)
-                .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
-                        eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
-                        eq(UserHandle.USER_CURRENT)));
-
+        // Tests are set up with manual brightness by default, so no need to set it here.
         DisplayPowerRequest dpr = new DisplayPowerRequest();
         dpr.policy = DisplayPowerRequest.POLICY_OFF;
         mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
@@ -637,11 +625,10 @@
 
     @Test
     public void testSetScreenOffBrightnessSensorDisabled_DisplayIsDisabled() {
-        doAnswer((Answer<Integer>) invocationOnMock ->
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
-                .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
-                        eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
-                        eq(UserHandle.USER_CURRENT)));
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
         mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ false);
 
         DisplayPowerRequest dpr = new DisplayPowerRequest();
@@ -695,9 +682,10 @@
     public void testBrightnessNitsPersistWhenDisplayDeviceChanges() {
         float brightness = 0.3f;
         float nits = 500;
-        when(mResourcesMock.getBoolean(
-                com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay))
-                .thenReturn(true);
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay,
+                true);
+
         mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
         when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
 
@@ -758,7 +746,7 @@
                 any(HysteresisLevels.class),
                 any(HysteresisLevels.class),
                 any(HysteresisLevels.class),
-                eq(mContextSpy),
+                eq(mContext),
                 any(HighBrightnessModeController.class),
                 any(BrightnessThrottler.class),
                 isNull(),
@@ -866,7 +854,7 @@
         setUpDisplay(displayId, uniqueId, display, device, config, isEnabled);
 
         final DisplayPowerController dpc = new DisplayPowerController(
-                mContextSpy, injector, mDisplayPowerCallbacksMock, mHandler,
+                mContext, injector, mDisplayPowerCallbacksMock, mHandler,
                 mSensorManagerMock, mDisplayBlankerMock, display,
                 mBrightnessTrackerMock, brightnessSetting, () -> {},
                 hbmMetadata, /* bootCompleted= */ false);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java
index df2f59a..e24354f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java
@@ -149,7 +149,8 @@
                 .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
                         eq(notificationId), eq(notification), eq(UserHandle.getUserId(uid)));
 
-        coordinator.removeNotificationAssociation(jsc, JobParameters.STOP_REASON_UNDEFINED);
+        coordinator.removeNotificationAssociation(jsc, JobParameters.STOP_REASON_UNDEFINED,
+                jsc.getRunningJobLocked());
         verify(mNotificationManagerInternal, never())
                 .cancelNotification(anyString(), anyString(), anyInt(), anyInt(), any(),
                         anyInt(), anyInt());
@@ -170,7 +171,8 @@
                 .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
                         eq(notificationId), eq(notification), eq(UserHandle.getUserId(uid)));
 
-        coordinator.removeNotificationAssociation(jsc, JobParameters.STOP_REASON_UNDEFINED);
+        coordinator.removeNotificationAssociation(jsc, JobParameters.STOP_REASON_UNDEFINED,
+                jsc.getRunningJobLocked());
         verify(mNotificationManagerInternal)
                 .cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
                         eq(notificationId), eq(UserHandle.getUserId(uid)));
@@ -292,7 +294,8 @@
                         eq(notificationId2), eq(notification2), eq(UserHandle.getUserId(uid)));
 
         // Remove the first job. Only the first notification should be removed.
-        coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_UNDEFINED);
+        coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_UNDEFINED,
+                jsc1.getRunningJobLocked());
         inOrder.verify(mNotificationManagerInternal)
                 .cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
                         eq(notificationId1), eq(UserHandle.getUserId(uid)));
@@ -300,7 +303,8 @@
                 .cancelNotification(anyString(), anyString(), anyInt(), anyInt(), any(),
                         eq(notificationId2), anyInt());
 
-        coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_UNDEFINED);
+        coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_UNDEFINED,
+                jsc2.getRunningJobLocked());
         inOrder.verify(mNotificationManagerInternal)
                 .cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
                         eq(notificationId2), eq(UserHandle.getUserId(uid)));
@@ -335,12 +339,14 @@
                         eq(notificationId), eq(notification2), eq(UserHandle.getUserId(uid)));
 
         // Remove the first job. The notification shouldn't be touched because of the 2nd job.
-        coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_UNDEFINED);
+        coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_UNDEFINED,
+                jsc1.getRunningJobLocked());
         inOrder.verify(mNotificationManagerInternal, never())
                 .cancelNotification(anyString(), anyString(), anyInt(), anyInt(), any(),
                         anyInt(), anyInt());
 
-        coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_UNDEFINED);
+        coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_UNDEFINED,
+                jsc2.getRunningJobLocked());
         inOrder.verify(mNotificationManagerInternal)
                 .cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
                         eq(notificationId), eq(UserHandle.getUserId(uid)));
@@ -376,7 +382,8 @@
                         eq(notificationId), eq(notification2), eq(UserHandle.getUserId(uid2)));
 
         // Remove the first job. Only the first notification should be removed.
-        coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_UNDEFINED);
+        coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_UNDEFINED,
+                jsc1.getRunningJobLocked());
         inOrder.verify(mNotificationManagerInternal)
                 .cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid1), eq(pid), any(),
                         eq(notificationId), eq(UserHandle.getUserId(uid1)));
@@ -384,7 +391,8 @@
                 .cancelNotification(anyString(), anyString(), eq(uid2), anyInt(), any(),
                         anyInt(), anyInt());
 
-        coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_UNDEFINED);
+        coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_UNDEFINED,
+                jsc2.getRunningJobLocked());
         inOrder.verify(mNotificationManagerInternal)
                 .cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid2), eq(pid), any(),
                         eq(notificationId), eq(UserHandle.getUserId(uid2)));
@@ -421,7 +429,8 @@
                         eq(notificationId), eq(notification2), eq(UserHandle.getUserId(uid)));
 
         // Remove the first job. Only the first notification should be removed.
-        coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_UNDEFINED);
+        coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_UNDEFINED,
+                jsc1.getRunningJobLocked());
         inOrder.verify(mNotificationManagerInternal)
                 .cancelNotification(eq(pkg1), eq(pkg1), eq(uid), eq(pid), any(),
                         eq(notificationId), eq(UserHandle.getUserId(uid)));
@@ -429,7 +438,8 @@
                 .cancelNotification(anyString(), anyString(), eq(uid), anyInt(), any(),
                         anyInt(), anyInt());
 
-        coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_UNDEFINED);
+        coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_UNDEFINED,
+                jsc2.getRunningJobLocked());
         inOrder.verify(mNotificationManagerInternal)
                 .cancelNotification(eq(pkg2), eq(pkg2), eq(uid), eq(pid), any(),
                         eq(notificationId), eq(UserHandle.getUserId(uid)));
@@ -450,7 +460,8 @@
                 .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
                         eq(notificationId), eq(notification), eq(UserHandle.getUserId(uid)));
 
-        coordinator.removeNotificationAssociation(jsc, JobParameters.STOP_REASON_USER);
+        coordinator.removeNotificationAssociation(jsc, JobParameters.STOP_REASON_USER,
+                jsc.getRunningJobLocked());
         verify(mNotificationManagerInternal)
                 .cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
                         eq(notificationId), eq(UserHandle.getUserId(uid)));
@@ -485,12 +496,14 @@
                         eq(notificationId), eq(notification2), eq(UserHandle.getUserId(uid)));
 
         // Remove the first job. The notification shouldn't be touched because of the 2nd job.
-        coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_USER);
+        coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_USER,
+                jsc1.getRunningJobLocked());
         inOrder.verify(mNotificationManagerInternal, never())
                 .cancelNotification(anyString(), anyString(), anyInt(), anyInt(), any(),
                         anyInt(), anyInt());
 
-        coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_USER);
+        coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_USER,
+                jsc2.getRunningJobLocked());
         inOrder.verify(mNotificationManagerInternal)
                 .cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
                         eq(notificationId), eq(UserHandle.getUserId(uid)));
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java
index 02fdfad..754f409 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java
@@ -276,9 +276,9 @@
         assertFalse(mThermalStatusRestriction.isJobRestricted(ejRunning));
         assertTrue(mThermalStatusRestriction.isJobRestricted(ejRunningLong));
         assertFalse(mThermalStatusRestriction.isJobRestricted(ui));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(uiRetried));
+        assertFalse(mThermalStatusRestriction.isJobRestricted(uiRetried));
         assertFalse(mThermalStatusRestriction.isJobRestricted(uiRunning));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(uiRunningLong));
+        assertFalse(mThermalStatusRestriction.isJobRestricted(uiRunningLong));
 
         mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_SEVERE);
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssAntennaInfoProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssAntennaInfoProviderTest.java
new file mode 100644
index 0000000..e1fa8f52
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssAntennaInfoProviderTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.server.location.gnss;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.location.LocationManager;
+import android.location.LocationManagerInternal;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+import com.android.server.location.gnss.hal.FakeGnssHal;
+import com.android.server.location.gnss.hal.GnssNative;
+import com.android.server.location.injector.Injector;
+import com.android.server.location.injector.TestInjector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GnssAntennaInfoProviderTest {
+    private @Mock Context mContext;
+    private @Mock LocationManagerInternal mInternal;
+    private @Mock GnssConfiguration mMockConfiguration;
+    private @Mock IBinder mBinder;
+    private GnssNative mGnssNative;
+
+    private GnssAntennaInfoProvider mTestProvider;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER),
+                anyInt());
+        LocalServices.addService(LocationManagerInternal.class, mInternal);
+        FakeGnssHal fakeGnssHal = new FakeGnssHal();
+        GnssNative.setGnssHalForTest(fakeGnssHal);
+        Injector injector = new TestInjector(mContext);
+        mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration)));
+        mTestProvider = new GnssAntennaInfoProvider(mGnssNative);
+        mGnssNative.register();
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(LocationManagerInternal.class);
+    }
+
+    @Test
+    public void testOnHalStarted() {
+        verify(mGnssNative, times(1)).startAntennaInfoListening();
+    }
+
+    @Test
+    public void testOnHalRestarted() {
+        mTestProvider.onHalRestarted();
+        verify(mGnssNative, times(2)).startAntennaInfoListening();
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java
index fd9dfe8..bf96b1d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java
@@ -74,7 +74,6 @@
     private @Mock Context mContext;
     private @Mock LocationManagerInternal mInternal;
     private @Mock GnssConfiguration mMockConfiguration;
-    private @Mock GnssNative.GeofenceCallbacks mGeofenceCallbacks;
     private @Mock IGnssMeasurementsListener mListener1;
     private @Mock IGnssMeasurementsListener mListener2;
     private @Mock IBinder mBinder1;
@@ -98,7 +97,6 @@
         Injector injector = new TestInjector(mContext);
         mGnssNative = spy(Objects.requireNonNull(
                 GnssNative.create(injector, mMockConfiguration)));
-        mGnssNative.setGeofenceCallbacks(mGeofenceCallbacks);
         mTestProvider = new GnssMeasurementsProvider(injector, mGnssNative);
         mGnssNative.register();
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNavigationMessageProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNavigationMessageProviderTest.java
new file mode 100644
index 0000000..64aa4b3
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNavigationMessageProviderTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.server.location.gnss;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.location.IGnssNavigationMessageListener;
+import android.location.LocationManager;
+import android.location.LocationManagerInternal;
+import android.location.util.identity.CallerIdentity;
+import android.os.IBinder;
+
+import com.android.server.LocalServices;
+import com.android.server.location.gnss.hal.FakeGnssHal;
+import com.android.server.location.gnss.hal.GnssNative;
+import com.android.server.location.injector.FakeUserInfoHelper;
+import com.android.server.location.injector.Injector;
+import com.android.server.location.injector.TestInjector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+
+public class GnssNavigationMessageProviderTest {
+    private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID;
+    private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1000,
+            "mypackage", "attribution", "listener");
+    private @Mock Context mContext;
+    private @Mock LocationManagerInternal mInternal;
+    private @Mock GnssConfiguration mMockConfiguration;
+    private @Mock IGnssNavigationMessageListener mListener;
+    private @Mock IBinder mBinder;
+
+    private GnssNative mGnssNative;
+
+    private GnssNavigationMessageProvider mTestProvider;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        doReturn(mBinder).when(mListener).asBinder();
+        doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER),
+                anyInt());
+        LocalServices.addService(LocationManagerInternal.class, mInternal);
+        FakeGnssHal fakeGnssHal = new FakeGnssHal();
+        GnssNative.setGnssHalForTest(fakeGnssHal);
+        Injector injector = new TestInjector(mContext);
+        mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration)));
+        mTestProvider = new GnssNavigationMessageProvider(injector, mGnssNative);
+        mGnssNative.register();
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(LocationManagerInternal.class);
+    }
+
+    @Test
+    public void testAddListener() {
+        // add a request
+        mTestProvider.addListener(IDENTITY, mListener);
+        verify(mGnssNative, times(1)).startNavigationMessageCollection();
+
+        // remove a request
+        mTestProvider.removeListener(mListener);
+        verify(mGnssNative, times(1)).stopNavigationMessageCollection();
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNmeaProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNmeaProviderTest.java
new file mode 100644
index 0000000..49e5e69
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNmeaProviderTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.server.location.gnss;
+
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.location.IGnssNmeaListener;
+import android.location.LocationManager;
+import android.location.LocationManagerInternal;
+import android.location.util.identity.CallerIdentity;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.LocalServices;
+import com.android.server.location.gnss.hal.FakeGnssHal;
+import com.android.server.location.gnss.hal.GnssNative;
+import com.android.server.location.injector.FakeUserInfoHelper;
+import com.android.server.location.injector.Injector;
+import com.android.server.location.injector.TestInjector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GnssNmeaProviderTest {
+
+    private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID;
+    private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1000,
+            "mypackage", "attribution", "listener");
+    private @Mock Context mContext;
+    private @Mock LocationManagerInternal mInternal;
+    private @Mock GnssConfiguration mMockConfiguration;
+    private @Mock IGnssNmeaListener mListener;
+    private @Mock IBinder mBinder;
+
+    private GnssNative mGnssNative;
+
+    private GnssNmeaProvider mTestProvider;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        doReturn(mBinder).when(mListener).asBinder();
+        doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER),
+                anyInt());
+        LocalServices.addService(LocationManagerInternal.class, mInternal);
+        FakeGnssHal fakeGnssHal = new FakeGnssHal();
+        GnssNative.setGnssHalForTest(fakeGnssHal);
+        Injector injector = new TestInjector(mContext);
+        mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration)));
+        mTestProvider = new GnssNmeaProvider(injector, mGnssNative);
+        mGnssNative.register();
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(LocationManagerInternal.class);
+    }
+
+    @Test
+    public void testAddListener() {
+        // add a request
+        mTestProvider.addListener(IDENTITY, mListener);
+        verify(mGnssNative, times(1)).startNmeaMessageCollection();
+
+        // remove a request
+        mTestProvider.removeListener(mListener);
+        verify(mGnssNative, times(1)).stopNmeaMessageCollection();
+    }
+
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssStatusProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssStatusProviderTest.java
new file mode 100644
index 0000000..ce2aec7f
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssStatusProviderTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.server.location.gnss;
+
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.location.IGnssStatusListener;
+import android.location.LocationManager;
+import android.location.LocationManagerInternal;
+import android.location.util.identity.CallerIdentity;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.LocalServices;
+import com.android.server.location.gnss.hal.FakeGnssHal;
+import com.android.server.location.gnss.hal.GnssNative;
+import com.android.server.location.injector.FakeUserInfoHelper;
+import com.android.server.location.injector.Injector;
+import com.android.server.location.injector.TestInjector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GnssStatusProviderTest {
+    private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID;
+    private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1000,
+            "mypackage", "attribution", "listener");
+    private @Mock Context mContext;
+    private @Mock LocationManagerInternal mInternal;
+    private @Mock GnssConfiguration mMockConfiguration;
+    private @Mock IGnssStatusListener mListener;
+    private @Mock IBinder mBinder;
+
+    private GnssNative mGnssNative;
+
+    private GnssStatusProvider mTestProvider;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        doReturn(mBinder).when(mListener).asBinder();
+        doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER),
+                anyInt());
+        LocalServices.addService(LocationManagerInternal.class, mInternal);
+        FakeGnssHal fakeGnssHal = new FakeGnssHal();
+        GnssNative.setGnssHalForTest(fakeGnssHal);
+        Injector injector = new TestInjector(mContext);
+        mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration)));
+        mTestProvider = new GnssStatusProvider(injector, mGnssNative);
+        mGnssNative.register();
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(LocationManagerInternal.class);
+    }
+
+    @Test
+    public void testAddListener() {
+        // add a request
+        mTestProvider.addListener(IDENTITY, mListener);
+        verify(mGnssNative, times(1)).startSvStatusCollection();
+
+        // remove a request
+        mTestProvider.removeListener(mListener);
+        verify(mGnssNative, times(1)).stopSvStatusCollection();
+    }
+
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java
index b7ab6f80..2d962ac 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java
@@ -562,6 +562,26 @@
     }
 
     @Override
+    protected boolean startSvStatusCollection() {
+        return true;
+    }
+
+    @Override
+    protected boolean stopSvStatusCollection() {
+        return true;
+    }
+
+    @Override
+    public boolean startNmeaMessageCollection() {
+        return true;
+    }
+
+    @Override
+    public boolean stopNmeaMessageCollection() {
+        return true;
+    }
+
+    @Override
     protected int getBatchSize() {
         return mBatchSize;
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
new file mode 100644
index 0000000..541b077
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
@@ -0,0 +1,248 @@
+/*
+ * 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.server.rollback;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.VersionedPackage;
+import android.content.rollback.PackageRollbackInfo;
+import android.content.rollback.RollbackInfo;
+import android.content.rollback.RollbackManager;
+import android.util.Log;
+import android.util.Xml;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.server.PackageWatchdog;
+import com.android.server.SystemConfig;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.List;
+import java.util.Scanner;
+
+
+@RunWith(AndroidJUnit4.class)
+public class RollbackPackageHealthObserverTest {
+    @Mock
+    private Context mMockContext;
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private PackageWatchdog mMockPackageWatchdog;
+    @Mock
+    RollbackManager mRollbackManager;
+    @Mock
+    RollbackInfo mRollbackInfo;
+    @Mock
+    PackageRollbackInfo mPackageRollbackInfo;
+
+    private MockitoSession mSession;
+    private static final String APP_A = "com.package.a";
+    private static final long VERSION_CODE = 1L;
+    private static final String LOG_TAG = "RollbackPackageHealthObserverTest";
+
+    private SystemConfig mSysConfig;
+
+    @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+    @Before
+    public void setup() {
+        mSysConfig = new SystemConfigTestClass();
+
+        mSession = ExtendedMockito.mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.LENIENT)
+                .spyStatic(PackageWatchdog.class)
+                .startMocking();
+
+        // Mock PackageWatchdog
+        doAnswer((Answer<PackageWatchdog>) invocationOnMock -> mMockPackageWatchdog)
+                .when(() -> PackageWatchdog.getInstance(mMockContext));
+
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mSession.finishMocking();
+    }
+
+    /**
+     * Subclass of SystemConfig without running the constructor.
+     */
+    private class SystemConfigTestClass extends SystemConfig {
+        SystemConfigTestClass() {
+            super(false);
+        }
+    }
+
+    @Test
+    public void testHealthCheckLevels() {
+        RollbackPackageHealthObserver observer =
+                spy(new RollbackPackageHealthObserver(mMockContext));
+        VersionedPackage testFailedPackage = new VersionedPackage(APP_A, VERSION_CODE);
+
+
+        when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+
+        // Crashes with no rollbacks available
+        assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
+                observer.onHealthCheckFailed(null,
+                        PackageWatchdog.FAILURE_REASON_NATIVE_CRASH, 1));
+        assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
+                observer.onHealthCheckFailed(null,
+                        PackageWatchdog.FAILURE_REASON_APP_CRASH, 1));
+
+        // Make the rollbacks available
+        when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(mRollbackInfo));
+        when(mRollbackInfo.getPackages()).thenReturn(List.of(mPackageRollbackInfo));
+        when(mPackageRollbackInfo.getVersionRolledBackFrom()).thenReturn(testFailedPackage);
+
+        // native crash
+        assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_30,
+                observer.onHealthCheckFailed(null,
+                        PackageWatchdog.FAILURE_REASON_NATIVE_CRASH, 1));
+        // non-native crash
+        assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_30,
+                observer.onHealthCheckFailed(testFailedPackage,
+                        PackageWatchdog.FAILURE_REASON_APP_CRASH, 1));
+        // Second non-native crash again
+        assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
+                observer.onHealthCheckFailed(testFailedPackage,
+                        PackageWatchdog.FAILURE_REASON_APP_CRASH, 2));
+        // Subsequent crashes when rollbacks have completed
+        when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of());
+        assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
+                observer.onHealthCheckFailed(testFailedPackage,
+                        PackageWatchdog.FAILURE_REASON_APP_CRASH, 3));
+    }
+
+    /**
+     * Test that isAutomaticRollbackDenied works correctly when packages that are not
+     * denied are sent.
+     */
+    @Test
+    public void isRollbackAllowedTest_false() throws IOException {
+        final String contents =
+                "<config>\n"
+                + "    <automatic-rollback-denylisted-app package=\"com.android.vending\" />\n"
+                + "</config>";
+        final File folder = createTempSubfolder("folder");
+        createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents);
+
+        readPermissions(folder, /* Grant all permission flags */ ~0);
+
+        assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
+                new VersionedPackage("com.test.package", 1))).isEqualTo(false);
+    }
+
+    /**
+     * Test that isAutomaticRollbackDenied works correctly when packages that are
+     * denied are sent.
+     */
+    @Test
+    public void isRollbackAllowedTest_true() throws IOException {
+        final String contents =
+                "<config>\n"
+                + "    <automatic-rollback-denylisted-app package=\"com.android.vending\" />\n"
+                + "</config>";
+        final File folder = createTempSubfolder("folder");
+        createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents);
+
+        readPermissions(folder, /* Grant all permission flags */ ~0);
+
+        assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
+                new VersionedPackage("com.android.vending", 1))).isEqualTo(true);
+    }
+
+    /**
+     * Test that isAutomaticRollbackDenied works correctly when no config is present
+     */
+    @Test
+    public void isRollbackAllowedTest_noConfig() throws IOException {
+        final File folder = createTempSubfolder("folder");
+
+        readPermissions(folder, /* Grant all permission flags */ ~0);
+
+        assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
+                new VersionedPackage("com.android.vending", 1))).isEqualTo(false);
+    }
+
+    /**
+     * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents.
+     *
+     * @param folder   pre-existing subdirectory of mTemporaryFolder to put the file
+     * @param fileName name of the file (e.g. filename.xml) to create
+     * @param contents contents to write to the file
+     * @return the newly created file
+     */
+    private File createTempFile(File folder, String fileName, String contents)
+            throws IOException {
+        File file = new File(folder, fileName);
+        BufferedWriter bw = new BufferedWriter(new FileWriter(file));
+        bw.write(contents);
+        bw.close();
+
+        // Print to logcat for test debugging.
+        Log.d(LOG_TAG, "Contents of file " + file.getAbsolutePath());
+        Scanner input = new Scanner(file);
+        while (input.hasNextLine()) {
+            Log.d(LOG_TAG, input.nextLine());
+        }
+
+        return file;
+    }
+
+    private void readPermissions(File libraryDir, int permissionFlag) {
+        final XmlPullParser parser = Xml.newPullParser();
+        mSysConfig.readPermissions(parser, libraryDir, permissionFlag);
+    }
+
+    /**
+     * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents.
+     *
+     * @param folderName subdirectory of mTemporaryFolder to put the file, creating if needed
+     * @return the folder
+     */
+    private File createTempSubfolder(String folderName)
+            throws IOException {
+        File folder = new File(mTemporaryFolder.getRoot(), folderName);
+        folder.mkdirs();
+        return folder;
+    }
+}
diff --git a/services/tests/servicestests/res/xml/irq_device_map_3.xml b/services/tests/servicestests/res/xml/irq_device_map_3.xml
index 1d2a7d3..7e2529a 100644
--- a/services/tests/servicestests/res/xml/irq_device_map_3.xml
+++ b/services/tests/servicestests/res/xml/irq_device_map_3.xml
@@ -23,4 +23,7 @@
     <device name="test.wifi.device">
         <subsystem>Wifi</subsystem>
     </device>
+    <device name="test.sound_trigger.device">
+        <subsystem>Sound_trigger</subsystem>
+    </device>
 </irq-device-map>
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index dbf5021..26a3ae1 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -43,6 +43,7 @@
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.ITrustManager;
 import android.content.Context;
+import android.content.res.Resources;
 import android.hardware.biometrics.BiometricManager.Authenticators;
 import android.hardware.biometrics.IBiometricAuthenticator;
 import android.hardware.biometrics.IBiometricSensorReceiver;
@@ -52,6 +53,7 @@
 import android.hardware.biometrics.SensorProperties;
 import android.hardware.face.FaceSensorProperties;
 import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorProperties;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.os.Binder;
@@ -83,6 +85,7 @@
     private static final long TEST_REQUEST_ID = 22;
 
     @Mock private Context mContext;
+    @Mock private Resources mResources;
     @Mock private BiometricContext mBiometricContext;
     @Mock private ITrustManager mTrustManager;
     @Mock private DevicePolicyManager mDevicePolicyManager;
@@ -104,6 +107,7 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        when(mContext.getResources()).thenReturn(mResources);
         when(mClientReceiver.asBinder()).thenReturn(mock(Binder.class));
         when(mBiometricContext.updateContext(any(), anyBoolean()))
                 .thenAnswer(invocation -> invocation.getArgument(0));
@@ -342,6 +346,33 @@
         testInvokesCancel(session -> session.onDialogDismissed(DISMISSED_REASON_NEGATIVE, null));
     }
 
+    @Test
+    public void testCallbackOnAcquired() throws RemoteException {
+        final String acquiredStr = "test_acquired_info_callback";
+        final String acquiredStrVendor = "test_acquired_info_callback_vendor";
+        setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_REAR);
+
+        final AuthSession session = createAuthSession(mSensors,
+                false /* checkDevicePolicyManager */,
+                Authenticators.BIOMETRIC_STRONG,
+                TEST_REQUEST_ID,
+                0 /* operationId */,
+                0 /* userId */);
+
+        when(mContext.getString(com.android.internal.R.string.fingerprint_acquired_partial))
+            .thenReturn(acquiredStr);
+        session.onAcquired(0, FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL, 0);
+        verify(mStatusBarService).onBiometricHelp(anyInt(), eq(acquiredStr));
+        verify(mClientReceiver).onAcquired(eq(1), eq(acquiredStr));
+
+        when(mResources.getStringArray(com.android.internal.R.array.fingerprint_acquired_vendor))
+            .thenReturn(new String[]{acquiredStrVendor});
+        session.onAcquired(0, FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR, 0);
+        verify(mStatusBarService).onBiometricHelp(anyInt(), eq(acquiredStrVendor));
+        verify(mClientReceiver).onAcquired(
+                eq(FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR_BASE), eq(acquiredStrVendor));
+    }
+
     // TODO (b/208484275) : Enable these tests
     // @Test
     // public void testPreAuth_canAuthAndPrivacyDisabled() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
index 7468901..d5d06d3 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics.sensors.face.aidl;
 
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT;
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -148,6 +150,28 @@
     }
 
     @Test
+    public void testLockoutEndsOperation() throws RemoteException {
+        final FaceAuthenticationClient client = createClient(2);
+        client.start(mCallback);
+        client.onLockoutPermanent();
+
+        verify(mClientMonitorCallbackConverter).onError(anyInt(), anyInt(),
+                eq(FACE_ERROR_LOCKOUT_PERMANENT), anyInt());
+        verify(mCallback).onClientFinished(client, false);
+    }
+
+    @Test
+    public void testTemporaryLockoutEndsOperation() throws RemoteException {
+        final FaceAuthenticationClient client = createClient(2);
+        client.start(mCallback);
+        client.onLockoutTimed(1000);
+
+        verify(mClientMonitorCallbackConverter).onError(anyInt(), anyInt(),
+                eq(FACE_ERROR_LOCKOUT), anyInt());
+        verify(mCallback).onClientFinished(client, false);
+    }
+
+    @Test
     public void notifyHalWhenContextChanges() throws RemoteException {
         final FaceAuthenticationClient client = createClient();
         client.start(mCallback);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
index 120ddf6..b539a76 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
@@ -86,6 +86,7 @@
 
     // Test setting default restrictions for managed profile.
     @Test
+    @Ignore("b/277916462")
     public void testMigration_managedProfileOwner() throws Exception {
         // Create a managed profile user.
         final File user10dir = getServices().addUser(10, 0,
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index dd81abe..16aadac 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -1368,6 +1368,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testClearDeviceOwner() throws Exception {
         mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
         mContext.callerPermissions.add(permission.MANAGE_USERS);
@@ -1955,6 +1956,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetUserRestriction_asDo() throws Exception {
         mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
         mContext.callerPermissions.add(permission.MANAGE_USERS);
@@ -2123,6 +2125,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetUserRestriction_asPo() {
         setAsProfileOwner(admin1);
 
@@ -2259,6 +2262,7 @@
             );
 
     @Test
+    @Ignore("b/277916462")
     public void testSetUserRestriction_asPoOfOrgOwnedDevice() throws Exception {
         final int MANAGED_PROFILE_ADMIN_UID =
                 UserHandle.getUid(CALLER_USER_HANDLE, DpmMockContext.SYSTEM_UID);
@@ -2334,6 +2338,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testNoDefaultEnabledUserRestrictions() throws Exception {
         mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
         mContext.callerPermissions.add(permission.MANAGE_USERS);
@@ -2925,6 +2930,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testCreateAdminSupportIntent() throws Exception {
         // Setup device owner.
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
@@ -4993,6 +4999,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testWipeDataManagedProfileOnOrganizationOwnedDevice() throws Exception {
         setupProfileOwner();
         configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE);
@@ -7023,6 +7030,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetUserControlDisabledPackages_asDO() throws Exception {
         final List<String> testPackages = new ArrayList<>();
         testPackages.add("package_1");
@@ -7038,6 +7046,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetUserControlDisabledPackages_asPO() {
         final List<String> testPackages = new ArrayList<>();
         testPackages.add("package_1");
@@ -7776,6 +7785,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetUserRestriction_financeDo_validRestrictions_setsRestriction()
             throws Exception {
         setDeviceOwner();
@@ -7858,6 +7868,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetUninstallBlocked_financeDo_success() throws Exception {
         String packageName = "com.android.foo.package";
         setDeviceOwner();
@@ -7871,6 +7882,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetUserControlDisabledPackages_financeDo_success() throws Exception {
         List<String> packages = new ArrayList<>();
         packages.add("com.android.foo.package");
@@ -7960,6 +7972,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testAddPersistentPreferredActivity_financeDo_success() throws Exception {
         IntentFilter filter = new IntentFilter();
         ComponentName target = new ComponentName(admin2.getPackageName(), "test.class");
@@ -7975,6 +7988,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testClearPackagePersistentPreferredActvities_financeDo_success() throws Exception {
         String packageName = admin2.getPackageName();
         setDeviceOwner();
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
index 550204b..4cfbb95 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
@@ -105,6 +105,7 @@
         mMockPackageManager = mock(PackageManager.class);
         mMockPackageMonitor = mock(PackageMonitor.class);
 
+        doReturn(mMockContext).when(mMockContext).createContextAsUser(any(), anyInt());
         // For unit tests, set the default installer info
         doReturn(DEFAULT_INSTALL_SOURCE_INFO).when(mMockPackageManager)
                 .getInstallSourceInfo(anyString());
diff --git a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
index da9de25..e20f1e7 100644
--- a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
@@ -131,6 +131,7 @@
         doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
         doReturn(InstrumentationRegistry.getContext().getContentResolver())
                 .when(mMockContext).getContentResolver();
+        doReturn(mMockContext).when(mMockContext).createContextAsUser(any(), anyInt());
 
         mStoragefile = new AtomicFile(new File(
                 Environment.getExternalStorageDirectory(), "systemUpdateUnitTests.xml"));
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index a0fb3de..21a11bc 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -763,7 +763,7 @@
                 ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class);
         verify(mDreamManagerInternalMock).registerDreamManagerStateListener(
                 dreamManagerStateListener.capture());
-        dreamManagerStateListener.getValue().onKeepDreamingWhenUndockedChanged(false);
+        dreamManagerStateListener.getValue().onKeepDreamingWhenUnpluggingChanged(false);
 
         when(mBatteryManagerInternalMock.getPlugType())
                 .thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK);
@@ -793,7 +793,7 @@
                 ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class);
         verify(mDreamManagerInternalMock).registerDreamManagerStateListener(
                 dreamManagerStateListener.capture());
-        dreamManagerStateListener.getValue().onKeepDreamingWhenUndockedChanged(true);
+        dreamManagerStateListener.getValue().onKeepDreamingWhenUnpluggingChanged(true);
 
         when(mBatteryManagerInternalMock.getPlugType())
                 .thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK);
@@ -823,7 +823,7 @@
                 ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class);
         verify(mDreamManagerInternalMock).registerDreamManagerStateListener(
                 dreamManagerStateListener.capture());
-        dreamManagerStateListener.getValue().onKeepDreamingWhenUndockedChanged(true);
+        dreamManagerStateListener.getValue().onKeepDreamingWhenUnpluggingChanged(true);
 
         when(mBatteryManagerInternalMock.getPlugType())
                 .thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK);
@@ -831,7 +831,7 @@
 
         forceAwake();  // Needs to be awake first before it can dream.
         forceDream();
-        dreamManagerStateListener.getValue().onKeepDreamingWhenUndockedChanged(false);
+        dreamManagerStateListener.getValue().onKeepDreamingWhenUnpluggingChanged(false);
         when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0);
         setPluggedIn(false);
 
@@ -839,6 +839,34 @@
     }
 
     @Test
+    public void testWakefulnessDream_shouldStopDreamingWhenUnplugging_whenDreamPrevents() {
+        // Make sure "unplug turns on screen" is configured to true.
+        when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen))
+                .thenReturn(true);
+
+        createService();
+        startSystem();
+
+        ArgumentCaptor<DreamManagerInternal.DreamManagerStateListener> dreamManagerStateListener =
+                ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class);
+        verify(mDreamManagerInternalMock).registerDreamManagerStateListener(
+                dreamManagerStateListener.capture());
+
+        when(mBatteryManagerInternalMock.getPlugType())
+                .thenReturn(BatteryManager.BATTERY_PLUGGED_AC);
+        setPluggedIn(true);
+
+        forceAwake();  // Needs to be awake first before it can dream.
+        forceDream();
+        dreamManagerStateListener.getValue().onKeepDreamingWhenUnpluggingChanged(false);
+        when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0);
+        setPluggedIn(false);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
+
+
+    @Test
     public void testWakefulnessDoze_goToSleep() {
         createService();
         // Start with AWAKE state
diff --git a/services/tests/servicestests/src/com/android/server/power/ShutdownCheckPointsTest.java b/services/tests/servicestests/src/com/android/server/power/ShutdownCheckPointsTest.java
index 2230ddd..2bde51b 100644
--- a/services/tests/servicestests/src/com/android/server/power/ShutdownCheckPointsTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/ShutdownCheckPointsTest.java
@@ -103,21 +103,45 @@
     }
 
     @Test
-    public void testCallerProcessBinderEntry() throws RemoteException {
+    public void testCallerProcessBinderEntries() throws RemoteException {
         List<ActivityManager.RunningAppProcessInfo> runningAppProcessInfos = new ArrayList<>();
         runningAppProcessInfos.add(
                 new ActivityManager.RunningAppProcessInfo("process_name", 1, new String[0]));
         when(mActivityManager.getRunningAppProcesses()).thenReturn(runningAppProcessInfos);
 
         mTestInjector.setCurrentTime(1000);
+        // Matching pid in getRunningAppProcesses
+        mInstance.recordCheckPointInternal(1, "reason1");
+        // Mising pid in getRunningAppProcesses
+        mInstance.recordCheckPointInternal(2, "reason2");
+
+        assertEquals(
+                "Shutdown request from BINDER for reason reason1 "
+                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
+                        + "com.android.server.power.ShutdownCheckPointsTest"
+                        + ".testCallerProcessBinderEntries\n"
+                        + "From process process_name (pid=1)\n\n"
+                        + "Shutdown request from BINDER for reason reason2 "
+                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
+                        + "com.android.server.power.ShutdownCheckPointsTest"
+                        + ".testCallerProcessBinderEntries\n"
+                        + "From process ? (pid=2)\n\n",
+                dumpToString());
+    }
+
+    @Test
+    public void testNullCallerProcessBinderEntries() throws RemoteException {
+        when(mActivityManager.getRunningAppProcesses()).thenReturn(null);
+
+        mTestInjector.setCurrentTime(1000);
         mInstance.recordCheckPointInternal(1, "reason1");
 
         assertEquals(
                 "Shutdown request from BINDER for reason reason1 "
                         + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
                         + "com.android.server.power.ShutdownCheckPointsTest"
-                        + ".testCallerProcessBinderEntry\n"
-                        + "From process process_name (pid=1)\n\n",
+                        + ".testNullCallerProcessBinderEntries\n"
+                        + "From process ? (pid=1)\n\n",
                 dumpToString());
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java
similarity index 80%
rename from services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
rename to services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java
index 7cf5bc8..dca67d6 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java
@@ -14,13 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.wakeups;
 
 import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_ALARM;
+import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER;
 import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_UNKNOWN;
 import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_WIFI;
 
-import static com.android.server.power.stats.CpuWakeupStats.WAKEUP_REASON_HALF_WINDOW_MS;
+import static com.android.server.power.stats.wakeups.CpuWakeupStats.WAKEUP_REASON_HALF_WINDOW_MS;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -34,6 +35,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import com.android.frameworks.servicestests.R;
+import com.android.server.power.stats.wakeups.CpuWakeupStats.Wakeup;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -46,10 +48,11 @@
 @RunWith(AndroidJUnit4.class)
 public class CpuWakeupStatsTest {
     private static final String KERNEL_REASON_ALARM_IRQ = "120 test.alarm.device";
-    private static final String KERNEL_REASON_WIFI_IRQ = "120 test.wifi.device";
+    private static final String KERNEL_REASON_WIFI_IRQ = "130 test.wifi.device";
+    private static final String KERNEL_REASON_SOUND_TRIGGER_IRQ = "129 test.sound_trigger.device";
     private static final String KERNEL_REASON_UNKNOWN_IRQ = "140 test.unknown.device";
     private static final String KERNEL_REASON_UNKNOWN = "free-form-reason test.alarm.device";
-    private static final String KERNEL_REASON_UNSUPPORTED = "-1 test.alarm.device";
+    private static final String KERNEL_REASON_ALARM_ABNORMAL = "-1 test.alarm.device";
     private static final String KERNEL_REASON_ABORT = "Abort: due to test.alarm.device";
 
     private static final int TEST_UID_1 = 13239823;
@@ -140,6 +143,40 @@
     }
 
     @Test
+    public void soundTriggerIrqAttributionSolo() {
+        final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
+        final long wakeupTime = 1247121;
+
+        populateDefaultProcStates(obj);
+
+        obj.noteWakeupTimeAndReason(wakeupTime, 1, KERNEL_REASON_SOUND_TRIGGER_IRQ);
+
+        // Outside the window, so should be ignored.
+        obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER,
+                wakeupTime - WAKEUP_REASON_HALF_WINDOW_MS - 1, TEST_UID_1);
+        obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER,
+                wakeupTime + WAKEUP_REASON_HALF_WINDOW_MS + 1, TEST_UID_2);
+        // Should be attributed
+        obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER, wakeupTime + 5, TEST_UID_3,
+                TEST_UID_5);
+
+        final SparseArray<SparseIntArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
+        assertThat(attribution).isNotNull();
+        assertThat(attribution.size()).isEqualTo(1);
+        assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER)).isTrue();
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER).indexOfKey(
+                TEST_UID_1)).isLessThan(0);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER).indexOfKey(
+                TEST_UID_2)).isLessThan(0);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER).get(TEST_UID_3)).isEqualTo(
+                TEST_PROC_STATE_3);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER).indexOfKey(
+                TEST_UID_4)).isLessThan(0);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER).get(TEST_UID_5)).isEqualTo(
+                TEST_PROC_STATE_5);
+    }
+
+    @Test
     public void wifiIrqAttributionSolo() {
         final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
         final long wakeupTime = 12423121;
@@ -268,22 +305,39 @@
     }
 
     @Test
-    public void unsupportedWakeupIgnored() {
+    public void abnormalAlarmAttribution() {
         final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
+        populateDefaultProcStates(obj);
 
         long wakeupTime = 970934;
-        obj.noteWakeupTimeAndReason(wakeupTime, 34, KERNEL_REASON_UNSUPPORTED);
+        obj.noteWakeupTimeAndReason(wakeupTime, 34, KERNEL_REASON_ALARM_ABNORMAL);
 
-        // Should be ignored as this type of wakeup is unsupported.
-        assertThat(obj.mWakeupEvents.size()).isEqualTo(0);
+        assertThat(obj.mWakeupEvents.size()).isEqualTo(1);
+        assertThat(obj.mWakeupEvents.valueAt(0).mType).isEqualTo(Wakeup.TYPE_ABNORMAL);
 
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3);
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime - 3, TEST_UID_4);
 
-        // Any nearby activity should not end up in the attribution map.
-        assertThat(obj.mWakeupAttribution.size()).isEqualTo(0);
+        final SparseArray<SparseIntArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
+        assertThat(attribution.size()).isEqualTo(1);
+        assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_ALARM)).isTrue();
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).indexOfKey(TEST_UID_1)).isLessThan(
+                0);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).indexOfKey(TEST_UID_2)).isLessThan(
+                0);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_3)).isEqualTo(
+                TEST_PROC_STATE_3);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_4)).isEqualTo(
+                TEST_PROC_STATE_4);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).indexOfKey(TEST_UID_5)).isLessThan(
+                0);
+    }
 
-        wakeupTime = 883124;
+    @Test
+    public void abortIgnored() {
+        final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
+
+        long wakeupTime = 883124;
         obj.noteWakeupTimeAndReason(wakeupTime, 3, KERNEL_REASON_ABORT);
 
         // Should be ignored as this type of wakeup is unsupported.
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/IrqDeviceMapTest.java b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/IrqDeviceMapTest.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/power/stats/IrqDeviceMapTest.java
rename to services/tests/servicestests/src/com/android/server/power/stats/wakeups/IrqDeviceMapTest.java
index 43d9e60..47a8f49 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/IrqDeviceMapTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/IrqDeviceMapTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.wakeups;
 
 import static com.google.common.truth.Truth.assertThat;
 
diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
deleted file mode 100644
index 0be678a..0000000
--- a/services/tests/servicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
+++ /dev/null
@@ -1,157 +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.server.rollback;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.pm.VersionedPackage;
-import android.util.Log;
-import android.util.Xml;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.SystemConfig;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.junit.runner.RunWith;
-import org.xmlpull.v1.XmlPullParser;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.Scanner;
-
-@RunWith(AndroidJUnit4.class)
-public class RollbackPackageHealthObserverTest {
-    private static final String LOG_TAG = "RollbackPackageHealthObserverTest";
-
-    private SystemConfig mSysConfig;
-
-    @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
-
-    @Before
-    public void setup() {
-        mSysConfig = new SystemConfigTestClass();
-    }
-
-    /**
-    * Subclass of SystemConfig without running the constructor.
-    */
-    private class SystemConfigTestClass extends SystemConfig {
-        SystemConfigTestClass() {
-          super(false);
-        }
-    }
-
-    /**
-     * Test that isAutomaticRollbackDenied works correctly when packages that are not
-     * denied are sent.
-     */
-    @Test
-    public void isRollbackAllowedTest_false() throws IOException {
-        final String contents =
-                "<config>\n"
-                + "    <automatic-rollback-denylisted-app package=\"com.android.vending\" />\n"
-                + "</config>";
-        final File folder = createTempSubfolder("folder");
-        createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents);
-
-        readPermissions(folder, /* Grant all permission flags */ ~0);
-
-        assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
-            new VersionedPackage("com.test.package", 1))).isEqualTo(false);
-    }
-
-    /**
-     * Test that isAutomaticRollbackDenied works correctly when packages that are
-     * denied are sent.
-     */
-    @Test
-    public void isRollbackAllowedTest_true() throws IOException {
-        final String contents =
-                "<config>\n"
-                + "    <automatic-rollback-denylisted-app package=\"com.android.vending\" />\n"
-                + "</config>";
-        final File folder = createTempSubfolder("folder");
-        createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents);
-
-        readPermissions(folder, /* Grant all permission flags */ ~0);
-
-        assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
-            new VersionedPackage("com.android.vending", 1))).isEqualTo(true);
-    }
-
-    /**
-     * Test that isAutomaticRollbackDenied works correctly when no config is present
-     */
-    @Test
-    public void isRollbackAllowedTest_noConfig() throws IOException {
-        final File folder = createTempSubfolder("folder");
-
-        readPermissions(folder, /* Grant all permission flags */ ~0);
-
-        assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
-            new VersionedPackage("com.android.vending", 1))).isEqualTo(false);
-    }
-
-    /**
-     * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents.
-     *
-     * @param folder   pre-existing subdirectory of mTemporaryFolder to put the file
-     * @param fileName name of the file (e.g. filename.xml) to create
-     * @param contents contents to write to the file
-     * @return the newly created file
-     */
-    private File createTempFile(File folder, String fileName, String contents)
-            throws IOException {
-        File file = new File(folder, fileName);
-        BufferedWriter bw = new BufferedWriter(new FileWriter(file));
-        bw.write(contents);
-        bw.close();
-
-        // Print to logcat for test debugging.
-        Log.d(LOG_TAG, "Contents of file " + file.getAbsolutePath());
-        Scanner input = new Scanner(file);
-        while (input.hasNextLine()) {
-            Log.d(LOG_TAG, input.nextLine());
-        }
-
-        return file;
-    }
-
-    private void readPermissions(File libraryDir, int permissionFlag) {
-        final XmlPullParser parser = Xml.newPullParser();
-        mSysConfig.readPermissions(parser, libraryDir, permissionFlag);
-    }
-
-    /**
-     * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents.
-     *
-     * @param folderName subdirectory of mTemporaryFolder to put the file, creating if needed
-     * @return the folder
-     */
-    private File createTempSubfolder(String folderName)
-            throws IOException {
-        File folder = new File(mTemporaryFolder.getRoot(), folderName);
-        folder.mkdirs();
-        return folder;
-    }
-}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
index ff6c976..516fb4a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -15,23 +15,31 @@
  */
 package com.android.server.notification;
 
-import static com.android.server.notification.GroupHelper.AUTOGROUP_KEY;
+import static android.app.Notification.FLAG_AUTO_CANCEL;
+import static android.app.Notification.FLAG_BUBBLE;
+import static android.app.Notification.FLAG_CAN_COLORIZE;
+import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
+import static android.app.Notification.FLAG_NO_CLEAR;
+import static android.app.Notification.FLAG_ONGOING_EVENT;
+
+import static com.android.server.notification.GroupHelper.BASE_FLAGS;
 
 import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
 
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 
+import android.annotation.SuppressLint;
 import android.app.Notification;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.util.ArrayMap;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -45,11 +53,10 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
-import java.util.LinkedHashSet;
 import java.util.List;
-import java.util.Map;
 
 @SmallTest
+@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the class.
 @RunWith(AndroidJUnit4.class)
 public class GroupHelperTest extends UiServiceTestCase {
     private @Mock GroupHelper.Callback mCallback;
@@ -82,21 +89,104 @@
     }
 
     @Test
-    public void testNoGroup_postingUnderLimit() throws Exception {
+    public void testGetAutogroupSummaryFlags_noChildren() {
+        ArrayMap<String, Integer> children = new ArrayMap<>();
+
+        assertEquals(BASE_FLAGS, mGroupHelper.getAutogroupSummaryFlags(children));
+    }
+
+    @Test
+    public void testGetAutogroupSummaryFlags_oneOngoing() {
+        ArrayMap<String, Integer> children = new ArrayMap<>();
+        children.put("a", 0);
+        children.put("b", FLAG_ONGOING_EVENT);
+        children.put("c", FLAG_BUBBLE);
+
+        assertEquals(FLAG_ONGOING_EVENT | BASE_FLAGS,
+                mGroupHelper.getAutogroupSummaryFlags(children));
+    }
+
+    @Test
+    public void testGetAutogroupSummaryFlags_oneOngoingNoClear() {
+        ArrayMap<String, Integer> children = new ArrayMap<>();
+        children.put("a", 0);
+        children.put("b", FLAG_ONGOING_EVENT|FLAG_NO_CLEAR);
+        children.put("c", FLAG_BUBBLE);
+
+        assertEquals(FLAG_NO_CLEAR | FLAG_ONGOING_EVENT | BASE_FLAGS,
+                mGroupHelper.getAutogroupSummaryFlags(children));
+    }
+
+    @Test
+    public void testGetAutogroupSummaryFlags_oneOngoingBubble() {
+        ArrayMap<String, Integer> children = new ArrayMap<>();
+        children.put("a", 0);
+        children.put("b", FLAG_ONGOING_EVENT | FLAG_BUBBLE);
+        children.put("c", FLAG_BUBBLE);
+
+        assertEquals(FLAG_ONGOING_EVENT | BASE_FLAGS,
+                mGroupHelper.getAutogroupSummaryFlags(children));
+    }
+
+    @Test
+    public void testGetAutogroupSummaryFlags_multipleOngoing() {
+        ArrayMap<String, Integer> children = new ArrayMap<>();
+        children.put("a", 0);
+        children.put("b", FLAG_ONGOING_EVENT);
+        children.put("c", FLAG_BUBBLE);
+        children.put("d", FLAG_ONGOING_EVENT);
+
+        assertEquals(FLAG_ONGOING_EVENT | BASE_FLAGS,
+                mGroupHelper.getAutogroupSummaryFlags(children));
+    }
+
+    @Test
+    public void testGetAutogroupSummaryFlags_oneAutoCancel() {
+        ArrayMap<String, Integer> children = new ArrayMap<>();
+        children.put("a", 0);
+        children.put("b", FLAG_AUTO_CANCEL);
+        children.put("c", FLAG_BUBBLE);
+
+        assertEquals(BASE_FLAGS,
+                mGroupHelper.getAutogroupSummaryFlags(children));
+    }
+
+    @Test
+    public void testGetAutogroupSummaryFlags_allAutoCancel() {
+        ArrayMap<String, Integer> children = new ArrayMap<>();
+        children.put("a", FLAG_AUTO_CANCEL);
+        children.put("b", FLAG_AUTO_CANCEL | FLAG_CAN_COLORIZE);
+        children.put("c", FLAG_AUTO_CANCEL);
+        children.put("d", FLAG_AUTO_CANCEL | FLAG_FOREGROUND_SERVICE);
+
+        assertEquals(FLAG_AUTO_CANCEL | BASE_FLAGS,
+                mGroupHelper.getAutogroupSummaryFlags(children));
+    }
+
+    @Test
+    public void testGetAutogroupSummaryFlags_allAutoCancelOneOngoing() {
+        ArrayMap<String, Integer> children = new ArrayMap<>();
+        children.put("a", FLAG_AUTO_CANCEL);
+        children.put("b", FLAG_AUTO_CANCEL | FLAG_CAN_COLORIZE);
+        children.put("c", FLAG_AUTO_CANCEL);
+        children.put("d", FLAG_AUTO_CANCEL | FLAG_FOREGROUND_SERVICE | FLAG_ONGOING_EVENT);
+
+        assertEquals(FLAG_AUTO_CANCEL| FLAG_ONGOING_EVENT | BASE_FLAGS,
+                mGroupHelper.getAutogroupSummaryFlags(children));
+    }
+
+    @Test
+    public void testNoGroup_postingUnderLimit() {
         final String pkg = "package";
         for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
             mGroupHelper.onNotificationPosted(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM),
                     false);
         }
-        verify(mCallback, never()).addAutoGroupSummary(
-                eq(UserHandle.USER_SYSTEM), eq(pkg), anyString(), anyBoolean());
-        verify(mCallback, never()).addAutoGroup(anyString());
-        verify(mCallback, never()).removeAutoGroup(anyString());
-        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+        verifyZeroInteractions(mCallback);
     }
 
     @Test
-    public void testNoGroup_multiPackage() throws Exception {
+    public void testNoGroup_multiPackage() {
         final String pkg = "package";
         final String pkg2 = "package2";
         for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
@@ -105,31 +195,23 @@
         }
         mGroupHelper.onNotificationPosted(
                 getSbn(pkg2, AUTOGROUP_AT_COUNT, "four", UserHandle.SYSTEM), false);
-        verify(mCallback, never()).addAutoGroupSummary(
-                eq(UserHandle.USER_SYSTEM), eq(pkg), anyString(), anyBoolean());
-        verify(mCallback, never()).addAutoGroup(anyString());
-        verify(mCallback, never()).removeAutoGroup(anyString());
-        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+        verifyZeroInteractions(mCallback);
     }
 
     @Test
-    public void testNoGroup_multiUser() throws Exception {
+    public void testNoGroup_multiUser() {
         final String pkg = "package";
         for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
             mGroupHelper.onNotificationPosted(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM),
                     false);
         }
         mGroupHelper.onNotificationPosted(
-                getSbn(pkg, AUTOGROUP_AT_COUNT, "four", UserHandle.ALL), false);
-        verify(mCallback, never()).addAutoGroupSummary(
-                anyInt(), eq(pkg), anyString(), anyBoolean());
-        verify(mCallback, never()).addAutoGroup(anyString());
-        verify(mCallback, never()).removeAutoGroup(anyString());
-        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+                getSbn(pkg, AUTOGROUP_AT_COUNT, "four", UserHandle.of(7)), false);
+        verifyZeroInteractions(mCallback);
     }
 
     @Test
-    public void testNoGroup_someAreGrouped() throws Exception {
+    public void testNoGroup_someAreGrouped() {
         final String pkg = "package";
         for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
             mGroupHelper.onNotificationPosted(
@@ -137,233 +219,344 @@
         }
         mGroupHelper.onNotificationPosted(
                 getSbn(pkg, AUTOGROUP_AT_COUNT, "four", UserHandle.SYSTEM, "a"), false);
-        verify(mCallback, never()).addAutoGroupSummary(
-                eq(UserHandle.USER_SYSTEM), eq(pkg), anyString(), anyBoolean());
-        verify(mCallback, never()).addAutoGroup(anyString());
-        verify(mCallback, never()).removeAutoGroup(anyString());
-        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+        verifyZeroInteractions(mCallback);
     }
 
     @Test
-    public void testPostingOverLimit() throws Exception {
+    public void testAddSummary() {
         final String pkg = "package";
         for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
             mGroupHelper.onNotificationPosted(
                     getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM), false);
         }
-        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), eq(false));
+        verify(mCallback, times(1)).addAutoGroupSummary(
+                anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
     }
 
     @Test
-    public void testPostingOverLimit_addsOngoingFlag() throws Exception {
+    public void testAddSummary_oneChildOngoing_summaryOngoing() {
         final String pkg = "package";
         for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
             StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
             if (i == 0) {
-                sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
+                sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
             }
             mGroupHelper.onNotificationPosted(sbn, false);
         }
-        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), eq(true));
+        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+                eq(BASE_FLAGS | FLAG_ONGOING_EVENT));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
     }
 
     @Test
-    public void testAutoGroupCount_addingNoGroupSBN() {
+    public void testAddSummary_oneChildAutoCancel_summaryNotAutoCancel() {
         final String pkg = "package";
-        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
-        for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
-            notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            if (i == 0) {
+                sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+            }
+            mGroupHelper.onNotificationPosted(sbn, false);
         }
-
-        for (StatusBarNotification sbn: notifications) {
-            sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
-            sbn.setOverrideGroupKey(AUTOGROUP_KEY);
-        }
-
-        for (StatusBarNotification sbn: notifications) {
-            mGroupHelper.onNotificationPosted(sbn, true);
-        }
-
-        verify(mCallback, times(AUTOGROUP_AT_COUNT + 1))
-            .updateAutogroupSummary(anyInt(), anyString(), eq(true));
-
-        int userId = UserHandle.SYSTEM.getIdentifier();
-        assertEquals(mGroupHelper.getOngoingGroupCount(
-                userId, pkg), AUTOGROUP_AT_COUNT + 1);
+        verify(mCallback, times(1)).addAutoGroupSummary(
+                anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
+        verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
     }
 
     @Test
-    public void testAutoGroupCount_UpdateNotification() {
+    public void testAddSummary_allChildrenAutoCancel_summaryAutoCancel() {
         final String pkg = "package";
-        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
-        for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
-            notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+            mGroupHelper.onNotificationPosted(sbn, false);
         }
-
-        for (StatusBarNotification sbn: notifications) {
-            sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
-            sbn.setOverrideGroupKey(AUTOGROUP_KEY);
-        }
-
-        for (StatusBarNotification sbn: notifications) {
-            mGroupHelper.onNotificationPosted(sbn, true);
-        }
-
-        notifications.get(0).getNotification().flags &= ~Notification.FLAG_ONGOING_EVENT;
-        mGroupHelper.onNotificationUpdated(notifications.get(0));
-
-        verify(mCallback, times(AUTOGROUP_AT_COUNT + 2))
-                .updateAutogroupSummary(anyInt(), anyString(), eq(true));
-
-        int userId = UserHandle.SYSTEM.getIdentifier();
-        assertEquals(mGroupHelper.getOngoingGroupCount(
-                userId, pkg), AUTOGROUP_AT_COUNT);
+        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+                eq(BASE_FLAGS | FLAG_AUTO_CANCEL));
+        verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
     }
 
     @Test
-    public void testAutoGroupCount_UpdateNotificationAfterChanges() {
+    public void testAddSummary_summaryAutoCancelNoClear() {
         final String pkg = "package";
-        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
-        for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
-            notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+            if (i == 0) {
+                sbn.getNotification().flags |= FLAG_NO_CLEAR;
+            }
+            mGroupHelper.onNotificationPosted(sbn, false);
         }
-
-        for (StatusBarNotification sbn: notifications) {
-            sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
-            sbn.setOverrideGroupKey(AUTOGROUP_KEY);
-        }
-
-        for (StatusBarNotification sbn: notifications) {
-            mGroupHelper.onNotificationPosted(sbn, true);
-        }
-
-        notifications.get(0).getNotification().flags &= ~Notification.FLAG_ONGOING_EVENT;
-
-        mGroupHelper.onNotificationUpdated(notifications.get(0));
-
-        notifications.get(0).getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
-
-        mGroupHelper.onNotificationUpdated(notifications.get(0));
-
-        verify(mCallback, times(AUTOGROUP_AT_COUNT + 3))
-                .updateAutogroupSummary(anyInt(), anyString(), eq(true));
-
-        int userId = UserHandle.SYSTEM.getIdentifier();
-        assertEquals(mGroupHelper.getOngoingGroupCount(
-                userId, pkg), AUTOGROUP_AT_COUNT + 1);
+        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+                eq(BASE_FLAGS | FLAG_AUTO_CANCEL | FLAG_NO_CLEAR));
+        verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
     }
 
     @Test
-    public void testAutoGroupCount_RemoveNotification() {
+    public void testAutoGrouped_allOngoing_updateChildNotOngoing() {
         final String pkg = "package";
+
+        // Post AUTOGROUP_AT_COUNT ongoing notifications
         ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
-        for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
-            notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
+            notifications.add(sbn);
         }
 
         for (StatusBarNotification sbn: notifications) {
-            sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
-            sbn.setOverrideGroupKey(AUTOGROUP_KEY);
+            mGroupHelper.onNotificationPosted(sbn, false);
+        }
+
+        // One notification is no longer ongoing
+        notifications.get(0).getNotification().flags &= ~FLAG_ONGOING_EVENT;
+        mGroupHelper.onNotificationPosted(notifications.get(0), true);
+
+        // Summary should keep FLAG_ONGOING_EVENT if any child has it
+        verify(mCallback).updateAutogroupSummary(
+                anyInt(), anyString(), eq(BASE_FLAGS | FLAG_ONGOING_EVENT));
+    }
+
+    @Test
+    public void testAutoGrouped_singleOngoing_removeOngoingChild() {
+        final String pkg = "package";
+
+        // Post AUTOGROUP_AT_COUNT ongoing notifications
+        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            if (i == 0) {
+                sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
+            }
+            notifications.add(sbn);
         }
 
         for (StatusBarNotification sbn: notifications) {
-            mGroupHelper.onNotificationPosted(sbn, true);
+            mGroupHelper.onNotificationPosted(sbn, false);
+        }
+
+        // remove ongoing
+        mGroupHelper.onNotificationRemoved(notifications.get(0));
+
+        // Summary is no longer ongoing
+        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS));
+    }
+
+    @Test
+    public void testAutoGrouped_noOngoing_updateOngoingChild() {
+        final String pkg = "package";
+
+        // Post AUTOGROUP_AT_COUNT ongoing notifications
+        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            notifications.add(sbn);
+        }
+
+        for (StatusBarNotification sbn: notifications) {
+            mGroupHelper.onNotificationPosted(sbn, false);
+        }
+
+        // update to ongoing
+        notifications.get(0).getNotification().flags |= FLAG_ONGOING_EVENT;
+        mGroupHelper.onNotificationPosted(notifications.get(0), true);
+
+        // Summary is now ongoing
+        verify(mCallback).updateAutogroupSummary(
+                anyInt(), anyString(), eq(BASE_FLAGS | FLAG_ONGOING_EVENT));
+    }
+
+    @Test
+    public void testAutoGrouped_noOngoing_addOngoingChild() {
+        final String pkg = "package";
+
+        // Post AUTOGROUP_AT_COUNT ongoing notifications
+        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            notifications.add(sbn);
+        }
+
+        for (StatusBarNotification sbn: notifications) {
+            mGroupHelper.onNotificationPosted(sbn, false);
+        }
+
+        // add ongoing
+        StatusBarNotification sbn = getSbn(pkg, AUTOGROUP_AT_COUNT + 1, null, UserHandle.SYSTEM);
+        sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
+        mGroupHelper.onNotificationPosted(sbn, true);
+
+        // Summary is now ongoing
+        verify(mCallback).updateAutogroupSummary(
+                anyInt(), anyString(), eq(BASE_FLAGS | FLAG_ONGOING_EVENT));
+    }
+
+    @Test
+    public void testAutoGrouped_singleOngoing_appGroupOngoingChild() {
+        final String pkg = "package";
+
+        // Post AUTOGROUP_AT_COUNT ongoing notifications
+        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            if (i == 0) {
+                sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
+            }
+            notifications.add(sbn);
+        }
+
+        for (StatusBarNotification sbn: notifications) {
+            mGroupHelper.onNotificationPosted(sbn, false);
+        }
+
+        // app group the ongoing child
+        StatusBarNotification sbn = getSbn(pkg, 0, "0", UserHandle.SYSTEM, "app group now");
+        mGroupHelper.onNotificationPosted(sbn, true);
+
+        // Summary is no longer ongoing
+        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS));
+    }
+
+    @Test
+    public void testAutoGrouped_singleOngoing_removeNonOngoingChild() {
+        final String pkg = "package";
+
+        // Post AUTOGROUP_AT_COUNT ongoing notifications
+        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            if (i == 0) {
+                sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
+            }
+            notifications.add(sbn);
+        }
+
+        for (StatusBarNotification sbn: notifications) {
+            mGroupHelper.onNotificationPosted(sbn, false);
+        }
+
+        // remove ongoing
+        mGroupHelper.onNotificationRemoved(notifications.get(1));
+
+        // Summary is still ongoing
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+    }
+
+    @Test
+    public void testAutoGrouped_allAutoCancel_updateChildNotAutoCancel() {
+        final String pkg = "package";
+
+        // Post AUTOGROUP_AT_COUNT ongoing notifications
+        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+            notifications.add(sbn);
+        }
+
+        for (StatusBarNotification sbn: notifications) {
+            mGroupHelper.onNotificationPosted(sbn, false);
+        }
+
+        // One notification is no longer autocancelable
+        notifications.get(0).getNotification().flags &= ~FLAG_AUTO_CANCEL;
+        mGroupHelper.onNotificationPosted(notifications.get(0), true);
+
+        // Summary should no longer be autocancelable
+        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS));
+    }
+
+    @Test
+    public void testAutoGrouped_almostAllAutoCancel_updateChildAutoCancel() {
+        final String pkg = "package";
+
+        // Post AUTOGROUP_AT_COUNT ongoing notifications
+        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            if (i != 0) {
+                sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+            }
+            notifications.add(sbn);
+        }
+
+        for (StatusBarNotification sbn: notifications) {
+            mGroupHelper.onNotificationPosted(sbn, false);
+        }
+
+        // Missing notification is now autocancelable
+        notifications.get(0).getNotification().flags |= FLAG_AUTO_CANCEL;
+        mGroupHelper.onNotificationPosted(notifications.get(0), true);
+
+        // Summary should now autocancelable
+        verify(mCallback).updateAutogroupSummary(
+                anyInt(), anyString(), eq(BASE_FLAGS | FLAG_AUTO_CANCEL));
+    }
+
+    @Test
+    public void testAutoGrouped_allAutoCancel_updateChildAppGrouped() {
+        final String pkg = "package";
+
+        // Post AUTOGROUP_AT_COUNT ongoing notifications
+        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+            notifications.add(sbn);
+        }
+
+        for (StatusBarNotification sbn: notifications) {
+            mGroupHelper.onNotificationPosted(sbn, false);
+        }
+
+        // One notification is now grouped by app
+        StatusBarNotification sbn = getSbn(pkg, 0, "0", UserHandle.SYSTEM, "app group now");
+        mGroupHelper.onNotificationPosted(sbn, true);
+
+        // Summary should be still be autocancelable
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+    }
+
+    @Test
+    public void testAutoGrouped_allAutoCancel_removeChild() {
+        final String pkg = "package";
+
+        // Post AUTOGROUP_AT_COUNT ongoing notifications
+        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+            sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+            notifications.add(sbn);
+        }
+
+        for (StatusBarNotification sbn: notifications) {
+            mGroupHelper.onNotificationPosted(sbn, false);
         }
 
         mGroupHelper.onNotificationRemoved(notifications.get(0));
 
-        verify(mCallback, times(AUTOGROUP_AT_COUNT + 2))
-                .updateAutogroupSummary(anyInt(), anyString(), eq(true));
-
-        int userId = UserHandle.SYSTEM.getIdentifier();
-        assertEquals(mGroupHelper.getOngoingGroupCount(
-                userId, pkg), AUTOGROUP_AT_COUNT);
-    }
-
-
-    @Test
-    public void testAutoGroupCount_UpdateToNoneOngoingNotification() {
-        final String pkg = "package";
-        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
-        for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
-            notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
-        }
-
-        for (StatusBarNotification sbn: notifications) {
-            sbn.setOverrideGroupKey(AUTOGROUP_KEY);
-        }
-
-        for (StatusBarNotification sbn: notifications) {
-            mGroupHelper.onNotificationPosted(sbn, true);
-        }
-
-        notifications.get(0).getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
-        mGroupHelper.onNotificationUpdated(notifications.get(0));
-
-        verify(mCallback, times(1))
-                .updateAutogroupSummary(anyInt(), anyString(), eq(true));
-
-        int userId = UserHandle.SYSTEM.getIdentifier();
-        assertEquals(mGroupHelper.getOngoingGroupCount(
-                userId, pkg), 1);
+        // Summary should still be autocancelable
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
     }
 
     @Test
-    public void testAutoGroupCount_AddOneOngoingNotification() {
-        final String pkg = "package";
-        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
-        for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
-            notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
-        }
-        StatusBarNotification sbn = notifications.get(AUTOGROUP_AT_COUNT);
-        sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
-        sbn.setOverrideGroupKey(AUTOGROUP_KEY);
-
-
-        for (StatusBarNotification current: notifications) {
-            mGroupHelper.onNotificationPosted(current, true);
-        }
-
-        verify(mCallback, times(1))
-                .updateAutogroupSummary(anyInt(), anyString(), eq(true));
-
-        int userId = UserHandle.SYSTEM.getIdentifier();
-        assertEquals(mGroupHelper.getOngoingGroupCount(
-                userId, pkg), 1);
-    }
-
-    @Test
-    public void testAutoGroupCount_UpdateNoneOngoing() {
-        final String pkg = "package";
-        ArrayList<StatusBarNotification>  notifications = new ArrayList<>();
-        for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
-            notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
-        }
-
-        for (StatusBarNotification sbn: notifications) {
-            sbn.setOverrideGroupKey(AUTOGROUP_KEY);
-        }
-
-        for (StatusBarNotification sbn: notifications) {
-            mGroupHelper.onNotificationPosted(sbn, true);
-        }
-
-        verify(mCallback, times(0))
-                .updateAutogroupSummary(anyInt(), anyString(), eq(true));
-
-        int userId = UserHandle.SYSTEM.getIdentifier();
-        assertEquals(mGroupHelper.getOngoingGroupCount(userId, pkg), 0);
-    }
-
-
-    @Test
-    public void testDropToZeroRemoveGroup() throws Exception {
+    public void testDropToZeroRemoveGroup() {
         final String pkg = "package";
         List<StatusBarNotification> posted = new ArrayList<>();
         for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
@@ -371,7 +564,8 @@
             posted.add(sbn);
             mGroupHelper.onNotificationPosted(sbn, false);
         }
-        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), eq(false));
+        verify(mCallback, times(1)).addAutoGroupSummary(
+                anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -390,7 +584,7 @@
     }
 
     @Test
-    public void testAppStartsGrouping() throws Exception {
+    public void testAppStartsGrouping() {
         final String pkg = "package";
         List<StatusBarNotification> posted = new ArrayList<>();
         for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
@@ -399,7 +593,7 @@
             mGroupHelper.onNotificationPosted(sbn, false);
         }
         verify(mCallback, times(1)).addAutoGroupSummary(
-                anyInt(), eq(pkg), anyString(), anyBoolean());
+                anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -408,9 +602,10 @@
         for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
             final StatusBarNotification sbn =
                     getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, "app group");
-            mGroupHelper.onNotificationPosted(sbn, false);
+            sbn.setOverrideGroupKey("autogrouped");
+            mGroupHelper.onNotificationPosted(sbn, true);
             verify(mCallback, times(1)).removeAutoGroup(sbn.getKey());
-            if (i < AUTOGROUP_AT_COUNT -1) {
+            if (i < AUTOGROUP_AT_COUNT - 1) {
                 verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
             }
         }
@@ -418,8 +613,7 @@
     }
 
     @Test
-    public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled()
-            throws Exception {
+    public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled() {
         final String pkg = "package";
         List<StatusBarNotification> posted = new ArrayList<>();
         for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
@@ -427,7 +621,8 @@
             posted.add(sbn);
             mGroupHelper.onNotificationPosted(sbn, false);
         }
-        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), eq(false));
+        verify(mCallback, times(1)).addAutoGroupSummary(
+                anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -441,10 +636,7 @@
         Mockito.reset(mCallback);
 
         // only one child remains
-        Map<String, LinkedHashSet<String>> ungroupedForUser =
-                mGroupHelper.mUngroupedNotifications.get(UserHandle.USER_SYSTEM);
-        assertNotNull(ungroupedForUser);
-        assertEquals(1, ungroupedForUser.get(pkg).size());
+        assertEquals(1, mGroupHelper.getNotGroupedByAppCount(UserHandle.USER_SYSTEM, pkg));
 
         // Add new notification; it should be autogrouped even though the total count is
         // < AUTOGROUP_AT_COUNT
@@ -454,5 +646,8 @@
         verify(mCallback, times(1)).addAutoGroup(sbn.getKey());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS));
+        verify(mCallback, never()).addAutoGroupSummary(
+                anyInt(), anyString(), anyString(), anyInt());
     }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index eceb589..9cfdaa7 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -754,13 +754,19 @@
 
     private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id,
             String groupKey, boolean isSummary) {
+        return generateNotificationRecord(channel, id, "tag" + System.currentTimeMillis(), groupKey,
+                isSummary);
+    }
+
+    private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id,
+            String tag, String groupKey, boolean isSummary) {
         Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
                 .setContentTitle("foo")
                 .setSmallIcon(android.R.drawable.sym_def_app_icon)
                 .setGroup(groupKey)
                 .setGroupSummary(isSummary);
         StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, id,
-                "tag" + System.currentTimeMillis(), mUid, 0,
+                tag, mUid, 0,
                 nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         return new NotificationRecord(mContext, sbn, channel);
     }
@@ -1899,7 +1905,8 @@
         mService.mSummaryByGroupKey.put("pkg", summary);
         mService.mAutobundledSummaries.put(0, new ArrayMap<>());
         mService.mAutobundledSummaries.get(0).put("pkg", summary.getKey());
-        mService.updateAutobundledSummaryFlags(0, "pkg", true, false);
+        mService.updateAutobundledSummaryFlags(
+                0, "pkg", GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT, false);
 
         assertTrue(summary.getSbn().isOngoing());
     }
@@ -1915,7 +1922,7 @@
         mService.mAutobundledSummaries.get(0).put("pkg", summary.getKey());
         mService.mSummaryByGroupKey.put("pkg", summary);
 
-        mService.updateAutobundledSummaryFlags(0, "pkg", false, false);
+        mService.updateAutobundledSummaryFlags(0, "pkg", GroupHelper.BASE_FLAGS, false);
 
         assertFalse(summary.getSbn().isOngoing());
     }
@@ -2897,7 +2904,7 @@
         when(mPermissionHelper.isPermissionFixed(PKG, temp.getUserId())).thenReturn(true);
 
         NotificationRecord r = mService.createAutoGroupSummary(
-                temp.getUserId(), temp.getSbn().getPackageName(), temp.getKey(), false);
+                temp.getUserId(), temp.getSbn().getPackageName(), temp.getKey(), 0);
 
         assertThat(r.isImportanceFixed()).isTrue();
     }
@@ -4213,7 +4220,7 @@
     }
 
     @Test
-    public void testOnlyAutogroupIfGroupChanged_noPriorNoti_autogroups() throws Exception {
+    public void testOnlyAutogroupIfNeeded_newNotification_ghUpdate() {
         NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, null, false);
         mService.addEnqueuedNotification(r);
         NotificationManagerService.PostNotificationRunnable runnable =
@@ -4226,17 +4233,18 @@
     }
 
     @Test
-    public void testOnlyAutogroupIfGroupChanged_groupChanged_autogroups()
-            throws Exception {
-        NotificationRecord r =
-                generateNotificationRecord(mTestNotificationChannel, 0, "group", false);
+    public void testOnlyAutogroupIfNeeded_groupChanged_ghUpdate() {
+        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0,
+                "testOnlyAutogroupIfNeeded_groupChanged_ghUpdate", "group", false);
         mService.addNotification(r);
 
-        r = generateNotificationRecord(mTestNotificationChannel, 0, null, false);
-        mService.addEnqueuedNotification(r);
+        NotificationRecord update = generateNotificationRecord(mTestNotificationChannel, 0,
+                "testOnlyAutogroupIfNeeded_groupChanged_ghUpdate", null, false);
+        mService.addEnqueuedNotification(update);
         NotificationManagerService.PostNotificationRunnable runnable =
-                mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
-                        r.getUid(), SystemClock.elapsedRealtime());
+                mService.new PostNotificationRunnable(update.getKey(),
+                        update.getSbn().getPackageName(), update.getUid(),
+                        SystemClock.elapsedRealtime());
         runnable.run();
         waitForIdle();
 
@@ -4244,16 +4252,39 @@
     }
 
     @Test
-    public void testOnlyAutogroupIfGroupChanged_noGroupChanged_autogroups()
-            throws Exception {
-        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, "group",
-                false);
+    public void testOnlyAutogroupIfNeeded_flagsChanged_ghUpdate() {
+        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0,
+                "testOnlyAutogroupIfNeeded_flagsChanged_ghUpdate", "group", false);
         mService.addNotification(r);
-        mService.addEnqueuedNotification(r);
+
+        NotificationRecord update = generateNotificationRecord(mTestNotificationChannel, 0,
+                "testOnlyAutogroupIfNeeded_flagsChanged_ghUpdate", null, false);
+        update.getNotification().flags = FLAG_AUTO_CANCEL;
+        mService.addEnqueuedNotification(update);
+        NotificationManagerService.PostNotificationRunnable runnable =
+                mService.new PostNotificationRunnable(update.getKey(),
+                        update.getSbn().getPackageName(), update.getUid(),
+                        SystemClock.elapsedRealtime());
+        runnable.run();
+        waitForIdle();
+
+        verify(mGroupHelper, times(1)).onNotificationPosted(any(), anyBoolean());
+    }
+
+    @Test
+    public void testOnlyAutogroupIfGroupChanged_noValidChange_noGhUpdate() {
+        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0,
+                "testOnlyAutogroupIfGroupChanged_noValidChange_noGhUpdate", null, false);
+        mService.addNotification(r);
+        NotificationRecord update = generateNotificationRecord(mTestNotificationChannel, 0,
+                "testOnlyAutogroupIfGroupChanged_noValidChange_noGhUpdate", null, false);
+        update.getNotification().color = Color.BLACK;
+        mService.addEnqueuedNotification(update);
 
         NotificationManagerService.PostNotificationRunnable runnable =
-                mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
-                        r.getUid(), SystemClock.elapsedRealtime());
+                mService.new PostNotificationRunnable(update.getKey(),
+                        update.getSbn().getPackageName(),
+                        update.getUid(), SystemClock.elapsedRealtime());
         runnable.run();
         waitForIdle();
 
@@ -10220,10 +10251,10 @@
 
         // grouphelper is a mock here, so make the calls it would make
 
-        // add summary; wait for it to be posted
-        mService.addAutoGroupSummary(nr1.getUserId(), nr1.getSbn().getPackageName(), nr1.getKey(),
-                true);
-        waitForIdle();
+        // add summary
+        mService.addNotification(mService.createAutoGroupSummary(nr1.getUserId(),
+                nr1.getSbn().getPackageName(), nr1.getKey(),
+                GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT));
 
         // cancel both children
         mBinderService.cancelNotificationWithTag(PKG, PKG, nr0.getSbn().getTag(),
@@ -10232,9 +10263,7 @@
                 nr1.getSbn().getId(), nr1.getSbn().getUserId());
         waitForIdle();
 
-        // group helper would send 'remove flag' and then 'remove summary' events
-        mService.updateAutobundledSummaryFlags(nr1.getUserId(), nr1.getSbn().getPackageName(),
-                false, false);
+        // group helper would send 'remove summary' event
         mService.clearAutogroupSummaryLocked(nr1.getUserId(), nr1.getSbn().getPackageName());
         waitForIdle();
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java
index e6569f7..9fe0e49 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java
@@ -98,6 +98,41 @@
     }
 
     @Test
+    public void testHasDiffs_autoBundled() {
+        NotificationRecord r = generateRecord();
+
+        NotificationRecordExtractorData extractorData = new NotificationRecordExtractorData(
+                1,
+                r.getPackageVisibilityOverride(),
+                r.canShowBadge(),
+                r.canBubble(),
+                r.getNotification().isBubbleNotification(),
+                r.getChannel(),
+                r.getGroupKey(),
+                r.getPeopleOverride(),
+                r.getSnoozeCriteria(),
+                r.getUserSentiment(),
+                r.getSuppressedVisualEffects(),
+                r.getSystemGeneratedSmartActions(),
+                r.getSmartReplies(),
+                r.getImportance(),
+                r.getRankingScore(),
+                r.isConversation(),
+                r.getProposedImportance(),
+                r.hasSensitiveContent());
+
+        Bundle signals = new Bundle();
+        signals.putString(Adjustment.KEY_GROUP_KEY, "ranker_group");
+        Adjustment adjustment = new Adjustment("pkg", r.getKey(), signals, "", 0);
+        r.addAdjustment(adjustment);
+        NotificationAdjustmentExtractor adjustmentExtractor = new NotificationAdjustmentExtractor();
+        adjustmentExtractor.process(r);
+
+        assertTrue(extractorData.hasDiffForRankingLocked(r, 1));
+        assertTrue(extractorData.hasDiffForLoggingLocked(r, 1));
+    }
+
+    @Test
     public void testHasDiffs_sensitiveContentChange() {
         NotificationRecord r = generateRecord();
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
new file mode 100644
index 0000000..bcd807a
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
@@ -0,0 +1,319 @@
+/*
+ * 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.server.notification;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.provider.Settings;
+import android.service.notification.Condition;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeDiff;
+import android.service.notification.ZenPolicy;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.ArrayMap;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class ZenModeDiffTest extends UiServiceTestCase {
+    // version is not included in the diff; manual & automatic rules have special handling
+    public static final Set<String> ZEN_MODE_CONFIG_EXEMPT_FIELDS =
+            Set.of("version", "manualRule", "automaticRules");
+
+    @Test
+    public void testRuleDiff_addRemoveSame() {
+        // Test add, remove, and both sides same
+        ZenModeConfig.ZenRule r = makeRule();
+
+        // Both sides same rule
+        ZenModeDiff.RuleDiff dSame = new ZenModeDiff.RuleDiff(r, r);
+        assertFalse(dSame.hasDiff());
+
+        // from existent rule to null: expect deleted
+        ZenModeDiff.RuleDiff deleted = new ZenModeDiff.RuleDiff(r, null);
+        assertTrue(deleted.hasDiff());
+        assertTrue(deleted.wasRemoved());
+
+        // from null to new rule: expect added
+        ZenModeDiff.RuleDiff added = new ZenModeDiff.RuleDiff(null, r);
+        assertTrue(added.hasDiff());
+        assertTrue(added.wasAdded());
+    }
+
+    @Test
+    public void testRuleDiff_fieldDiffs() throws Exception {
+        // Start these the same
+        ZenModeConfig.ZenRule r1 = makeRule();
+        ZenModeConfig.ZenRule r2 = makeRule();
+
+        // maps mapping field name -> expected output value as we set diffs
+        ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+        ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+        List<Field> fieldsForDiff = getFieldsForDiffCheck(
+                ZenModeConfig.ZenRule.class, Set.of()); // actually no exempt fields for ZenRule
+        generateFieldDiffs(r1, r2, fieldsForDiff, expectedFrom, expectedTo);
+
+        ZenModeDiff.RuleDiff d = new ZenModeDiff.RuleDiff(r1, r2);
+        assertTrue(d.hasDiff());
+
+        // Now diff them and check that each of the fields has a diff
+        for (Field f : fieldsForDiff) {
+            String name = f.getName();
+            assertNotNull("diff not found for field: " + name, d.getDiffForField(name));
+            assertTrue(d.getDiffForField(name).hasDiff());
+            assertTrue("unexpected field: " + name, expectedFrom.containsKey(name));
+            assertTrue("unexpected field: " + name, expectedTo.containsKey(name));
+            assertEquals(expectedFrom.get(name), d.getDiffForField(name).from());
+            assertEquals(expectedTo.get(name), d.getDiffForField(name).to());
+        }
+    }
+
+    @Test
+    public void testConfigDiff_addRemoveSame() {
+        // Default config, will test add, remove, and no change
+        ZenModeConfig c = new ZenModeConfig();
+
+        ZenModeDiff.ConfigDiff dSame = new ZenModeDiff.ConfigDiff(c, c);
+        assertFalse(dSame.hasDiff());
+
+        ZenModeDiff.ConfigDiff added = new ZenModeDiff.ConfigDiff(null, c);
+        assertTrue(added.hasDiff());
+        assertTrue(added.wasAdded());
+
+        ZenModeDiff.ConfigDiff removed = new ZenModeDiff.ConfigDiff(c, null);
+        assertTrue(removed.hasDiff());
+        assertTrue(removed.wasRemoved());
+    }
+
+    @Test
+    public void testConfigDiff_fieldDiffs() throws Exception {
+        // these two start the same
+        ZenModeConfig c1 = new ZenModeConfig();
+        ZenModeConfig c2 = new ZenModeConfig();
+
+        // maps mapping field name -> expected output value as we set diffs
+        ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+        ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+        List<Field> fieldsForDiff = getFieldsForDiffCheck(
+                ZenModeConfig.class, ZEN_MODE_CONFIG_EXEMPT_FIELDS);
+        generateFieldDiffs(c1, c2, fieldsForDiff, expectedFrom, expectedTo);
+
+        ZenModeDiff.ConfigDiff d = new ZenModeDiff.ConfigDiff(c1, c2);
+        assertTrue(d.hasDiff());
+
+        // Now diff them and check that each of the fields has a diff
+        for (Field f : fieldsForDiff) {
+            String name = f.getName();
+            assertNotNull("diff not found for field: " + name, d.getDiffForField(name));
+            assertTrue(d.getDiffForField(name).hasDiff());
+            assertTrue("unexpected field: " + name, expectedFrom.containsKey(name));
+            assertTrue("unexpected field: " + name, expectedTo.containsKey(name));
+            assertEquals(expectedFrom.get(name), d.getDiffForField(name).from());
+            assertEquals(expectedTo.get(name), d.getDiffForField(name).to());
+        }
+    }
+
+    @Test
+    public void testConfigDiff_specialSenders() {
+        // these two start the same
+        ZenModeConfig c1 = new ZenModeConfig();
+        ZenModeConfig c2 = new ZenModeConfig();
+
+        // set c1 and c2 to have some different senders
+        c1.allowMessagesFrom = ZenModeConfig.SOURCE_STAR;
+        c2.allowMessagesFrom = ZenModeConfig.SOURCE_CONTACT;
+        c1.allowConversationsFrom = ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
+        c2.allowConversationsFrom = ZenPolicy.CONVERSATION_SENDERS_NONE;
+
+        ZenModeDiff.ConfigDiff d = new ZenModeDiff.ConfigDiff(c1, c2);
+        assertTrue(d.hasDiff());
+
+        // Diff in top-level fields
+        assertTrue(d.getDiffForField("allowMessagesFrom").hasDiff());
+        assertTrue(d.getDiffForField("allowConversationsFrom").hasDiff());
+
+        // Bonus testing of stringification of people senders and conversation senders
+        assertTrue(d.toString().contains("allowMessagesFrom:stars->contacts"));
+        assertTrue(d.toString().contains("allowConversationsFrom:important->none"));
+    }
+
+    @Test
+    public void testConfigDiff_hasRuleDiffs() {
+        // two default configs
+        ZenModeConfig c1 = new ZenModeConfig();
+        ZenModeConfig c2 = new ZenModeConfig();
+
+        // two initially-identical rules
+        ZenModeConfig.ZenRule r1 = makeRule();
+        ZenModeConfig.ZenRule r2 = makeRule();
+
+        // one that will become a manual rule
+        ZenModeConfig.ZenRule m = makeRule();
+
+        // Add r1 to c1, but not r2 to c2 yet -- expect a rule to be deleted
+        c1.automaticRules.put(r1.id, r1);
+        ZenModeDiff.ConfigDiff deleteRule = new ZenModeDiff.ConfigDiff(c1, c2);
+        assertTrue(deleteRule.hasDiff());
+        assertNotNull(deleteRule.getAllAutomaticRuleDiffs());
+        assertTrue(deleteRule.getAllAutomaticRuleDiffs().containsKey("ruleId"));
+        assertTrue(deleteRule.getAllAutomaticRuleDiffs().get("ruleId").wasRemoved());
+
+        // Change r2 a little, add r2 to c2 as an automatic rule and m as a manual rule
+        r2.component = null;
+        r2.pkg = "different";
+        c2.manualRule = m;
+        c2.automaticRules.put(r2.id, r2);
+
+        // Expect diffs in both manual rule (added) and automatic rule (changed)
+        ZenModeDiff.ConfigDiff changed = new ZenModeDiff.ConfigDiff(c1, c2);
+        assertTrue(changed.hasDiff());
+        assertTrue(changed.getManualRuleDiff().hasDiff());
+
+        ArrayMap<String, ZenModeDiff.RuleDiff> automaticDiffs = changed.getAllAutomaticRuleDiffs();
+        assertNotNull(automaticDiffs);
+        assertTrue(automaticDiffs.containsKey("ruleId"));
+        assertNotNull(automaticDiffs.get("ruleId").getDiffForField("component"));
+        assertNull(automaticDiffs.get("ruleId").getDiffForField("component").to());
+        assertNotNull(automaticDiffs.get("ruleId").getDiffForField("pkg"));
+        assertEquals("different", automaticDiffs.get("ruleId").getDiffForField("pkg").to());
+    }
+
+    // Helper methods for working with configs, policies, rules
+    // Just makes a zen rule with fields filled in
+    private ZenModeConfig.ZenRule makeRule() {
+        ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
+        rule.configurationActivity = new ComponentName("a", "a");
+        rule.component = new ComponentName("b", "b");
+        rule.conditionId = new Uri.Builder().scheme("hello").build();
+        rule.condition = new Condition(rule.conditionId, "", Condition.STATE_TRUE);
+        rule.enabled = true;
+        rule.creationTime = 123;
+        rule.id = "ruleId";
+        rule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        rule.modified = false;
+        rule.name = "name";
+        rule.snoozing = true;
+        rule.pkg = "a";
+        return rule;
+    }
+
+    // Get the fields on which we would want to check a diff. The requirements are: not final or/
+    // static (as these should/can never change), and not in a specific list that's exempted.
+    private List<Field> getFieldsForDiffCheck(Class c, Set<String> exemptNames)
+            throws SecurityException {
+        Field[] fields = c.getDeclaredFields();
+        ArrayList<Field> out = new ArrayList<>();
+
+        for (Field field : fields) {
+            // Check for exempt reasons
+            int m = field.getModifiers();
+            if (Modifier.isFinal(m)
+                    || Modifier.isStatic(m)
+                    || exemptNames.contains(field.getName())) {
+                continue;
+            }
+            out.add(field);
+        }
+        return out;
+    }
+
+    // Generate a set of generic diffs for the specified two objects and the fields to generate
+    // diffs for, and store the results in the provided expectation maps to be able to check the
+    // output later.
+    private void generateFieldDiffs(Object a, Object b, List<Field> fields,
+            ArrayMap<String, Object> expectedA, ArrayMap<String, Object> expectedB)
+            throws Exception {
+        // different classes passed in means bad input
+        assertEquals(a.getClass(), b.getClass());
+
+        // Loop through fields for which we want to check diffs, set a diff and keep track of
+        // what we set.
+        for (Field f : fields) {
+            f.setAccessible(true);
+            // Just double-check also that the fields actually are for the class declared
+            assertEquals(f.getDeclaringClass(), a.getClass());
+            Class t = f.getType();
+            // handle the full set of primitive types first
+            if (boolean.class.equals(t)) {
+                f.setBoolean(a, true);
+                expectedA.put(f.getName(), true);
+                f.setBoolean(b, false);
+                expectedB.put(f.getName(), false);
+            } else if (int.class.equals(t)) {
+                // these are not actually valid going to be valid for arbitrary int enum fields, but
+                // we just put something in there regardless.
+                f.setInt(a, 2);
+                expectedA.put(f.getName(), 2);
+                f.setInt(b, 1);
+                expectedB.put(f.getName(), 1);
+            } else if (long.class.equals(t)) {
+                f.setLong(a, 200L);
+                expectedA.put(f.getName(), 200L);
+                f.setLong(b, 100L);
+                expectedB.put(f.getName(), 100L);
+            } else if (t.isPrimitive()) {
+                // This method doesn't yet handle other primitive types. If the relevant diff
+                // classes gain new fields of these types, please add another clause here.
+                fail("primitive type not handled by generateFieldDiffs: " + t.getName());
+            } else if (String.class.equals(t)) {
+                f.set(a, "string1");
+                expectedA.put(f.getName(), "string1");
+                f.set(b, "string2");
+                expectedB.put(f.getName(), "string2");
+            } else {
+                // catch-all for other types: have the field be "added"
+                f.set(a, null);
+                expectedA.put(f.getName(), null);
+                try {
+                    f.set(b, t.getDeclaredConstructor().newInstance());
+                    expectedB.put(f.getName(), t.getDeclaredConstructor().newInstance());
+                } catch (Exception e) {
+                    // No default constructor, or blithely attempting to construct something doesn't
+                    // work for some reason. If the default value isn't null, then keep it.
+                    if (f.get(b) != null) {
+                        expectedB.put(f.getName(), f.get(b));
+                    } else {
+                        // If we can't even rely on that, fail. Have the test-writer special case
+                        // something, as this is not able to be genericized.
+                        fail("could not generically construct value for field: " + f.getName());
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 6f9798e..b2a5401 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -69,8 +69,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.AutomaticZenRule;
@@ -78,7 +76,6 @@
 import android.app.NotificationManager.Policy;
 import android.content.ComponentName;
 import android.content.ContentResolver;
-import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
@@ -90,7 +87,6 @@
 import android.media.AudioSystem;
 import android.media.VolumePolicy;
 import android.net.Uri;
-import android.os.Binder;
 import android.os.Process;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -98,6 +94,7 @@
 import android.service.notification.Condition;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenModeConfig.ScheduleInfo;
+import android.service.notification.ZenModeDiff;
 import android.service.notification.ZenPolicy;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
@@ -877,7 +874,8 @@
         mZenModeHelperSpy.readXml(parser, false, UserHandle.USER_ALL);
 
         assertEquals("Config mismatch: current vs expected: "
-                + mZenModeHelperSpy.mConfig.diff(expected), expected, mZenModeHelperSpy.mConfig);
+                + new ZenModeDiff.ConfigDiff(mZenModeHelperSpy.mConfig, expected), expected,
+                mZenModeHelperSpy.mConfig);
     }
 
     @Test
@@ -1046,7 +1044,8 @@
 
         ZenModeConfig actual = mZenModeHelperSpy.mConfigs.get(10);
         assertEquals(
-                "Config mismatch: current vs expected: " + actual.diff(config10), config10, actual);
+                "Config mismatch: current vs expected: "
+                        + new ZenModeDiff.ConfigDiff(actual, config10), config10, actual);
         assertNotEquals("Expected config mismatch", config11, mZenModeHelperSpy.mConfigs.get(11));
     }
 
@@ -1062,7 +1061,8 @@
         mZenModeHelperSpy.readXml(parser, true, UserHandle.USER_SYSTEM);
 
         assertEquals("Config mismatch: current vs original: "
-                + mZenModeHelperSpy.mConfig.diff(original), original, mZenModeHelperSpy.mConfig);
+                + new ZenModeDiff.ConfigDiff(mZenModeHelperSpy.mConfig, original),
+                original, mZenModeHelperSpy.mConfig);
         assertEquals(original.hashCode(), mZenModeHelperSpy.mConfig.hashCode());
     }
 
@@ -1083,8 +1083,9 @@
 
         ZenModeConfig actual = mZenModeHelperSpy.mConfigs.get(10);
         expected.user = 10;
-        assertEquals(
-                "Config mismatch: current vs original: " + actual.diff(expected), expected, actual);
+        assertEquals("Config mismatch: current vs original: "
+                        + new ZenModeDiff.ConfigDiff(actual, expected),
+                expected, actual);
         assertEquals(expected.hashCode(), actual.hashCode());
         expected.user = 0;
         assertNotEquals(expected, mZenModeHelperSpy.mConfig);
diff --git a/services/tests/voiceinteractiontests/Android.bp b/services/tests/voiceinteractiontests/Android.bp
index 986fb71..e704ebf 100644
--- a/services/tests/voiceinteractiontests/Android.bp
+++ b/services/tests/voiceinteractiontests/Android.bp
@@ -40,6 +40,7 @@
         "platform-test-annotations",
         "services.core",
         "services.voiceinteraction",
+        "services.soundtrigger",
         "servicestests-core-utils",
         "servicestests-utils-mockito-extended",
         "truth-prebuilt",
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerDuplicateModelHandlerTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerDuplicateModelHandlerTest.java
new file mode 100644
index 0000000..bc94dac
--- /dev/null
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerDuplicateModelHandlerTest.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+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.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.media.soundtrigger.ModelParameterRange;
+import android.media.soundtrigger.Phrase;
+import android.media.soundtrigger.PhraseSoundModel;
+import android.media.soundtrigger.Properties;
+import android.media.soundtrigger.RecognitionConfig;
+import android.media.soundtrigger.RecognitionMode;
+import android.media.soundtrigger.SoundModel;
+import android.media.soundtrigger.SoundModelType;
+import android.media.soundtrigger.Status;
+import android.os.IBinder;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.InOrder;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(JUnit4.class)
+public final class SoundTriggerDuplicateModelHandlerTest {
+    // Component under test
+    private SoundTriggerDuplicateModelHandler mComponent;
+
+    private static final String DUPLICATE_UUID = "abcddead-beef-0123-3210-0123456789ab";
+    private static final String DIFFERENT_UUID = "0000dead-beef-0123-3210-0123456789ab";
+
+    @Mock private ISoundTriggerHal mUnderlying;
+    @Mock private ISoundTriggerHal.GlobalCallback mGlobalCallback;
+    @Mock private ISoundTriggerHal.ModelCallback mModelCallback;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mComponent = new SoundTriggerDuplicateModelHandler(mUnderlying);
+        doNothing().when(mUnderlying).registerCallback(any());
+        mComponent.registerCallback(mGlobalCallback);
+        verify(mUnderlying).registerCallback(eq(mGlobalCallback));
+    }
+
+    @Test
+    public void loadSoundModel_throwsResourceContention_whenDuplicateUuid() {
+        final var soundModel = createSoundModelOne();
+        final var soundModelSameUuid = createSoundModelTwo();
+        // First sound model load should complete successfully
+        mComponent.loadSoundModel(soundModel, mModelCallback);
+        verify(mUnderlying).loadSoundModel(eq(soundModel), eq(mModelCallback));
+        assertEquals(
+                assertThrows(
+                                RecoverableException.class,
+                                () -> mComponent.loadSoundModel(soundModelSameUuid, mModelCallback))
+                        .errorCode,
+                Status.RESOURCE_CONTENTION);
+        // Model has not been unloaded, so we don't get a callback
+        verify(mGlobalCallback, never()).onResourcesAvailable();
+        verifyNoMoreInteractions(mUnderlying);
+        verifyNoMoreInteractions(mGlobalCallback);
+    }
+
+    @Test
+    public void loadSoundModel_doesNotThrowResourceContention_whenDifferentUuid() {
+        final var soundModel = createSoundModelOne();
+        // Make all other fields the same
+        final var soundModelDifferentUuid = createSoundModelOne();
+        soundModelDifferentUuid.uuid = DIFFERENT_UUID;
+        InOrder inOrder = Mockito.inOrder(mUnderlying);
+        // First sound model load should complete successfully
+        mComponent.loadSoundModel(soundModel, mModelCallback);
+        inOrder.verify(mUnderlying).loadSoundModel(eq(soundModel), eq(mModelCallback));
+        mComponent.loadSoundModel(soundModelDifferentUuid, mModelCallback);
+        inOrder.verify(mUnderlying).loadSoundModel(eq(soundModelDifferentUuid), eq(mModelCallback));
+        // No contention, so we don't get a callback
+        verify(mGlobalCallback, never()).onResourcesAvailable();
+        verifyNoMoreInteractions(mUnderlying);
+        verifyNoMoreInteractions(mGlobalCallback);
+    }
+
+    @Test
+    public void loadSoundModel_doesNotThrow_afterDuplicateUuidHasBeenUnloaded() {
+        final var soundModel = createSoundModelOne();
+        // First sound model load should complete successfully
+        int handle = mComponent.loadSoundModel(soundModel, mModelCallback);
+        verify(mUnderlying).loadSoundModel(eq(soundModel), eq(mModelCallback));
+        // Unload model should complete successfully
+        mComponent.unloadSoundModel(handle);
+        verify(mUnderlying).unloadSoundModel(eq(handle));
+        // Since the model with the same UUID was unloaded, the subsequent load model
+        // should succeed.
+        mComponent.loadSoundModel(soundModel, mModelCallback);
+        verify(mUnderlying, times(2)).loadSoundModel(eq(soundModel), eq(mModelCallback));
+        verifyNoMoreInteractions(mUnderlying);
+        verifyNoMoreInteractions(mGlobalCallback);
+    }
+
+    @Test
+    public void unloadSoundModel_triggersResourceCallback_afterDuplicateUuidRejected() {
+        final var soundModel = createSoundModelOne();
+        final var soundModelSameUuid = createSoundModelTwo();
+        // First sound model load should complete successfully
+        int handle = mComponent.loadSoundModel(soundModel, mModelCallback);
+        verify(mUnderlying).loadSoundModel(eq(soundModel), eq(mModelCallback));
+        assertEquals(
+                assertThrows(
+                                RecoverableException.class,
+                                () -> mComponent.loadSoundModel(soundModelSameUuid, mModelCallback))
+                        .errorCode,
+                Status.RESOURCE_CONTENTION);
+        mComponent.unloadSoundModel(handle);
+        verify(mUnderlying).unloadSoundModel(eq(handle));
+        verify(mGlobalCallback).onResourcesAvailable();
+        verifyNoMoreInteractions(mUnderlying);
+        verifyNoMoreInteractions(mGlobalCallback);
+    }
+
+    // Next tests are same as above, but for phrase sound model.
+    @Test
+    public void loadPhraseSoundModel_throwsResourceContention_whenDuplicateUuid() {
+        final var soundModel = createPhraseSoundModelOne();
+        final var soundModelSameUuid = createPhraseSoundModelTwo();
+        // First sound model load should complete successfully
+        mComponent.loadPhraseSoundModel(soundModel, mModelCallback);
+        verify(mUnderlying).loadPhraseSoundModel(eq(soundModel), eq(mModelCallback));
+        assertEquals(
+                assertThrows(
+                                RecoverableException.class,
+                                () ->
+                                        mComponent.loadPhraseSoundModel(
+                                                soundModelSameUuid, mModelCallback))
+                        .errorCode,
+                Status.RESOURCE_CONTENTION);
+        // Model has not been unloaded, so we don't get a callback
+        verify(mGlobalCallback, never()).onResourcesAvailable();
+        verifyNoMoreInteractions(mUnderlying);
+        verifyNoMoreInteractions(mGlobalCallback);
+    }
+
+    @Test
+    public void loadPhraseSoundModel_doesNotThrowResourceContention_whenDifferentUuid() {
+        final var soundModel = createPhraseSoundModelOne();
+        // Make all other fields the same
+        final var soundModelDifferentUuid = createPhraseSoundModelOne();
+        soundModelDifferentUuid.common.uuid = DIFFERENT_UUID;
+        InOrder inOrder = Mockito.inOrder(mUnderlying);
+        // First sound model load should complete successfully
+        mComponent.loadPhraseSoundModel(soundModel, mModelCallback);
+        inOrder.verify(mUnderlying).loadPhraseSoundModel(eq(soundModel), eq(mModelCallback));
+        mComponent.loadPhraseSoundModel(soundModelDifferentUuid, mModelCallback);
+        inOrder.verify(mUnderlying).loadPhraseSoundModel(eq(soundModelDifferentUuid),
+                eq(mModelCallback));
+        // No contention, so we don't get a callback
+        verify(mGlobalCallback, never()).onResourcesAvailable();
+        verifyNoMoreInteractions(mUnderlying);
+        verifyNoMoreInteractions(mGlobalCallback);
+    }
+
+    @Test
+    public void loadPhraseSoundModel_doesNotThrow_afterDuplicateUuidHasBeenUnloaded() {
+        final var soundModel = createPhraseSoundModelOne();
+        // First sound model load should complete successfully
+        int handle = mComponent.loadPhraseSoundModel(soundModel, mModelCallback);
+        verify(mUnderlying).loadPhraseSoundModel(eq(soundModel), eq(mModelCallback));
+        // Unload model should complete successfully
+        mComponent.unloadSoundModel(handle);
+        verify(mUnderlying).unloadSoundModel(eq(handle));
+        // Since the model with the same UUID was unloaded, the subsequent load model
+        // should succeed.
+        mComponent.loadPhraseSoundModel(soundModel, mModelCallback);
+        verify(mUnderlying, times(2)).loadPhraseSoundModel(eq(soundModel), eq(mModelCallback));
+        verifyNoMoreInteractions(mUnderlying);
+        verifyNoMoreInteractions(mGlobalCallback);
+    }
+
+    @Test
+    public void unloadSoundModel_triggersResourceCallback_afterDuplicateUuidRejectedPhrase() {
+        final var soundModel = createPhraseSoundModelOne();
+        final var soundModelSameUuid = createPhraseSoundModelTwo();
+        // First sound model load should complete successfully
+        int handle = mComponent.loadPhraseSoundModel(soundModel, mModelCallback);
+        verify(mUnderlying).loadPhraseSoundModel(eq(soundModel), eq(mModelCallback));
+        assertEquals(
+                assertThrows(
+                                RecoverableException.class,
+                                () ->
+                                        mComponent.loadPhraseSoundModel(
+                                                soundModelSameUuid, mModelCallback))
+                        .errorCode,
+                Status.RESOURCE_CONTENTION);
+        mComponent.unloadSoundModel(handle);
+        verify(mUnderlying).unloadSoundModel(eq(handle));
+        verify(mGlobalCallback).onResourcesAvailable();
+        verifyNoMoreInteractions(mUnderlying);
+        verifyNoMoreInteractions(mGlobalCallback);
+    }
+
+    @Test
+    public void testDelegation() {
+        // Test that the rest of the interface delegates its calls to the underlying object
+        // appropriately.
+        // This test method does not test load/unloadSoundModel
+        var properties = new Properties();
+        InOrder inOrder = Mockito.inOrder(mUnderlying);
+        doReturn(properties).when(mUnderlying).getProperties();
+        assertEquals(mComponent.getProperties(), properties);
+        inOrder.verify(mUnderlying).getProperties();
+        var mockGlobalCallback = mock(ISoundTriggerHal.GlobalCallback.class);
+        mComponent.registerCallback(mockGlobalCallback);
+        inOrder.verify(mUnderlying).registerCallback(eq(mockGlobalCallback));
+        int modelId = 5;
+        int deviceHandle = 2;
+        int ioHandle = 3;
+        var config = mock(RecognitionConfig.class);
+        mComponent.startRecognition(modelId, deviceHandle, ioHandle, config);
+        inOrder.verify(mUnderlying)
+                .startRecognition(eq(modelId), eq(deviceHandle), eq(ioHandle), eq(config));
+
+        mComponent.stopRecognition(modelId);
+        inOrder.verify(mUnderlying).stopRecognition(eq(modelId));
+        mComponent.forceRecognitionEvent(modelId);
+        inOrder.verify(mUnderlying).forceRecognitionEvent(eq(modelId));
+        int param = 10;
+        int value = 50;
+        var modelParamRange = new ModelParameterRange();
+        doReturn(modelParamRange).when(mUnderlying).queryParameter(anyInt(), anyInt());
+        assertEquals(mComponent.queryParameter(param, value), modelParamRange);
+        inOrder.verify(mUnderlying).queryParameter(param, value);
+        doReturn(value).when(mUnderlying).getModelParameter(anyInt(), anyInt());
+        assertEquals(mComponent.getModelParameter(modelId, param), value);
+        inOrder.verify(mUnderlying).getModelParameter(eq(modelId), eq(param));
+        mComponent.setModelParameter(modelId, param, value);
+        inOrder.verify(mUnderlying).setModelParameter(eq(modelId), eq(param), eq(value));
+        var recipient = mock(IBinder.DeathRecipient.class);
+        mComponent.linkToDeath(recipient);
+        inOrder.verify(mUnderlying).linkToDeath(eq(recipient));
+        mComponent.unlinkToDeath(recipient);
+        inOrder.verify(mUnderlying).unlinkToDeath(eq(recipient));
+        mComponent.flushCallbacks();
+        inOrder.verify(mUnderlying).flushCallbacks();
+        var token = mock(IBinder.class);
+        mComponent.clientAttached(token);
+        inOrder.verify(mUnderlying).clientAttached(eq(token));
+        mComponent.clientDetached(token);
+        inOrder.verify(mUnderlying).clientDetached(eq(token));
+        mComponent.reboot();
+        inOrder.verify(mUnderlying).reboot();
+        mComponent.detach();
+        inOrder.verify(mUnderlying).detach();
+        verifyNoMoreInteractions(mUnderlying);
+        verifyNoMoreInteractions(mGlobalCallback);
+    }
+
+    private static SoundModel createSoundModelOne() {
+        SoundModel model = new SoundModel();
+        model.type = SoundModelType.GENERIC;
+        model.uuid = DUPLICATE_UUID;
+        model.vendorUuid = "87654321-5432-6543-7654-456789fedcba";
+        byte[] data = new byte[] {91, 92, 93, 94, 95};
+        model.data = TestUtil.byteArrayToParcelFileDescriptor(data);
+        model.dataSize = data.length;
+        return model;
+    }
+
+    // Different except for the same UUID
+    private static SoundModel createSoundModelTwo() {
+        SoundModel model = new SoundModel();
+        model.type = SoundModelType.GENERIC;
+        model.uuid = DUPLICATE_UUID;
+        model.vendorUuid = "12345678-9876-5432-1012-345678901234";
+        byte[] data = new byte[] {19, 18, 17, 16};
+        model.data = TestUtil.byteArrayToParcelFileDescriptor(data);
+        model.dataSize = data.length;
+        return model;
+    }
+
+    private static PhraseSoundModel createPhraseSoundModelOne() {
+        PhraseSoundModel model = new PhraseSoundModel();
+        model.common = createSoundModelOne();
+        model.common.type = SoundModelType.KEYPHRASE;
+        model.phrases = new Phrase[1];
+        model.phrases[0] = new Phrase();
+        model.phrases[0].id = 123;
+        model.phrases[0].users = new int[] {5, 6, 7};
+        model.phrases[0].locale = "locale";
+        model.phrases[0].text = "text";
+        model.phrases[0].recognitionModes =
+                RecognitionMode.USER_AUTHENTICATION | RecognitionMode.USER_IDENTIFICATION;
+        return model;
+    }
+
+    private static PhraseSoundModel createPhraseSoundModelTwo() {
+        PhraseSoundModel model = new PhraseSoundModel();
+        model.common = createSoundModelTwo();
+        model.common.type = SoundModelType.KEYPHRASE;
+        model.phrases = new Phrase[1];
+        model.phrases[0] = new Phrase();
+        model.phrases[0].id = 321;
+        model.phrases[0].users = new int[] {4, 3, 2, 1};
+        model.phrases[0].locale = "differentLocale";
+        model.phrases[0].text = "differentText";
+        model.phrases[0].recognitionModes = 0;
+        return model;
+    }
+}
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/TestUtil.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/TestUtil.java
index 39561f7..3b7bc95 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/TestUtil.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/TestUtil.java
@@ -466,7 +466,7 @@
         assertEquals(43, event.phraseExtras[0].levels[0].levelPercent);
     }
 
-    private static ParcelFileDescriptor byteArrayToParcelFileDescriptor(byte[] data) {
+    static ParcelFileDescriptor byteArrayToParcelFileDescriptor(byte[] data) {
         try (SharedMemory shmem = SharedMemory.create("", data.length)) {
             ByteBuffer buffer = shmem.mapReadWrite();
             buffer.put(data);
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index 2696d2b..f12b53a 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -87,6 +87,8 @@
                   android:showWhenLocked="true"/>
         <activity android:name="android.view.cts.surfacevalidator.CapturedActivity"/>
 
+        <activity android:name="com.android.server.wm.SurfaceControlViewHostTests$TestActivity" />
+
         <service android:name="android.view.cts.surfacevalidator.LocalMediaProjectionService"
             android:foregroundServiceType="mediaProjection"
             android:enabled="true">
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index c131c84..7092b0b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -104,7 +104,6 @@
         when(mMockRunner.asBinder()).thenReturn(new Binder());
         mController = spy(new RecentsAnimationController(mWm, mMockRunner, mAnimationCallbacks,
                 DEFAULT_DISPLAY));
-        mController.mShouldAttachNavBarToAppDuringTransition = false;
         mRootHomeTask = mDefaultDisplay.getDefaultTaskDisplayArea().getRootHomeTask();
         assertNotNull(mRootHomeTask);
     }
@@ -814,13 +813,13 @@
     }
 
     private void setupForShouldAttachNavBarDuringTransition() {
-        mController.mShouldAttachNavBarToAppDuringTransition = true;
         final WindowState navBar = spy(createWindow(null, TYPE_NAVIGATION_BAR, "NavigationBar"));
         mDefaultDisplay.getDisplayPolicy().addWindowLw(navBar, navBar.mAttrs);
         mWm.setRecentsAnimationController(mController);
         doReturn(navBar).when(mController).getNavigationBarWindow();
         final DisplayPolicy displayPolicy = spy(mDefaultDisplay.getDisplayPolicy());
         doReturn(displayPolicy).when(mDefaultDisplay).getDisplayPolicy();
+        doReturn(true).when(displayPolicy).shouldAttachNavBarToAppDuringTransition();
     }
 
     private static void initializeRecentsAnimationController(RecentsAnimationController controller,
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java
new file mode 100644
index 0000000..41bfc80
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java
@@ -0,0 +1,194 @@
+/*
+ * 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.server.wm;
+
+import static android.Manifest.permission.ACCESS_SURFACE_FLINGER;
+import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
+import static android.server.wm.CtsWindowInfoUtils.waitForWindowVisible;
+import static android.server.wm.CtsWindowInfoUtils.waitForWindowFocus;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.res.Configuration;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.view.Gravity;
+import android.view.IWindow;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.view.WindowlessWindowManager;
+import android.widget.Button;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+
+@Presubmit
+@SmallTest
+@RunWith(WindowTestRunner.class)
+public class SurfaceControlViewHostTests {
+    private final ActivityTestRule<TestActivity> mActivityRule = new ActivityTestRule<>(
+            TestActivity.class);
+    private Instrumentation mInstrumentation;
+    private TestActivity mActivity;
+
+    private View mView1;
+    private View mView2;
+    private SurfaceControlViewHost mScvh1;
+    private SurfaceControlViewHost mScvh2;
+
+    @Before
+    public void setUp() throws Exception {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+
+        // ACCESS_SURFACE_FLINGER is necessary to call waitForWindow
+        // INTERNAL_SYSTEM_WINDOW is necessary to add SCVH with no host parent
+        mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(ACCESS_SURFACE_FLINGER,
+                INTERNAL_SYSTEM_WINDOW);
+        mActivity = mActivityRule.launchActivity(null);
+    }
+
+    @After
+    public void tearDown() {
+        mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
+    }
+
+    @Test
+    public void requestFocusWithMultipleWindows() throws InterruptedException, RemoteException {
+        SurfaceControl sc = new SurfaceControl.Builder()
+                .setName("SurfaceControlViewHostTests")
+                .setCallsite("requestFocusWithMultipleWindows")
+                .build();
+        mView1 = new Button(mActivity);
+        mView2 = new Button(mActivity);
+
+        mActivity.runOnUiThread(() -> {
+            TestWindowlessWindowManager wwm = new TestWindowlessWindowManager(
+                    mActivity.getResources().getConfiguration(), sc, null);
+
+            try {
+                mActivity.attachToSurfaceView(sc);
+            } catch (InterruptedException e) {
+            }
+
+            mScvh1 = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(),
+                    wwm, "requestFocusWithMultipleWindows");
+            mScvh2 = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(),
+                    wwm, "requestFocusWithMultipleWindows");
+
+
+            mView1.setBackgroundColor(Color.RED);
+            mView2.setBackgroundColor(Color.BLUE);
+
+            WindowManager.LayoutParams lp1 = new WindowManager.LayoutParams(200, 200,
+                    TYPE_APPLICATION, 0, PixelFormat.OPAQUE);
+            WindowManager.LayoutParams lp2 = new WindowManager.LayoutParams(100, 100,
+                    TYPE_APPLICATION, 0, PixelFormat.OPAQUE);
+            mScvh1.setView(mView1, lp1);
+            mScvh2.setView(mView2, lp2);
+        });
+
+        assertTrue("Failed to wait for view1", waitForWindowVisible(mView1));
+        assertTrue("Failed to wait for view2", waitForWindowVisible(mView2));
+
+        WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
+                mScvh1.getFocusGrantToken(), true);
+        assertTrue("Failed to gain focus for view1", waitForWindowFocus(mView1, true));
+
+        WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
+                mScvh2.getFocusGrantToken(), true);
+        assertTrue("Failed to gain focus for view2", waitForWindowFocus(mView2, true));
+    }
+
+    private static class TestWindowlessWindowManager extends WindowlessWindowManager {
+        private final SurfaceControl mRoot;
+
+        TestWindowlessWindowManager(Configuration c, SurfaceControl rootSurface,
+                IBinder hostInputToken) {
+            super(c, rootSurface, hostInputToken);
+            mRoot = rootSurface;
+        }
+
+        @Override
+        protected SurfaceControl getParentSurface(IWindow window,
+                WindowManager.LayoutParams attrs) {
+            return mRoot;
+        }
+    }
+
+    public static class TestActivity extends Activity implements SurfaceHolder.Callback {
+        private SurfaceView mSurfaceView;
+        private final CountDownLatch mSvReadyLatch = new CountDownLatch(1);
+
+        @Override
+        protected void onCreate(@Nullable Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            final FrameLayout content = new FrameLayout(this);
+            mSurfaceView = new SurfaceView(this);
+            mSurfaceView.setBackgroundColor(Color.BLACK);
+            mSurfaceView.setZOrderOnTop(true);
+            final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(500, 500,
+                    Gravity.LEFT | Gravity.TOP);
+            content.addView(mSurfaceView, lp);
+            setContentView(content);
+            mSurfaceView.getHolder().addCallback(this);
+        }
+
+        @Override
+        public void surfaceCreated(@NonNull SurfaceHolder holder) {
+            mSvReadyLatch.countDown();
+        }
+
+        @Override
+        public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width,
+                int height) {
+        }
+
+        @Override
+        public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
+        }
+
+        public void attachToSurfaceView(SurfaceControl sc) throws InterruptedException {
+            mSvReadyLatch.await();
+            new SurfaceControl.Transaction().reparent(sc, mSurfaceView.getSurfaceControl())
+                    .show(sc).apply();
+        }
+    }
+}
+
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 1e2fdec..43b429c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1410,9 +1410,9 @@
         final Transition.ChangeInfo task1ChangeInfo = closeTransition.mChanges.get(task1);
         assertNotNull(task1ChangeInfo);
         assertTrue(task1ChangeInfo.hasChanged());
+        // Make sure the unrelated activity is NOT collected.
         final Transition.ChangeInfo activity1ChangeInfo = closeTransition.mChanges.get(activity1);
-        assertNotNull(activity1ChangeInfo);
-        assertTrue(activity1ChangeInfo.hasChanged());
+        assertNull(activity1ChangeInfo);
         // No need to wait for the activity in transient hide task.
         assertEquals(WindowContainer.SYNC_STATE_NONE, activity1.mSyncState);
 
@@ -1442,6 +1442,7 @@
                 }
             }
         });
+        assertTrue(activity1.isVisible());
         controller.finishTransition(closeTransition);
         assertTrue(wasInFinishingTransition[0]);
         assertNull(controller.mFinishingTransition);
@@ -1450,6 +1451,7 @@
         assertEquals(ActivityTaskManagerService.APP_SWITCH_DISALLOW, mAtm.getBalAppSwitchesState());
         // Because task1 is occluded by task2, finishTransition should make activity1 invisible.
         assertFalse(activity1.isVisibleRequested());
+        // Make sure activity1 visibility was committed
         assertFalse(activity1.isVisible());
         assertFalse(activity1.app.hasActivityInVisibleTask());
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
index 6cf2b2d..74ba45c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
@@ -500,7 +500,6 @@
         RecentsAnimationController controller = new RecentsAnimationController(
                 mWm, mockRunner, null, displayId);
         spyOn(controller);
-        controller.mShouldAttachNavBarToAppDuringTransition = true;
         doReturn(mNavBarWindow).when(controller).getNavigationBarWindow();
         mWm.setRecentsAnimationController(controller);
 
@@ -508,6 +507,10 @@
         spyOn(mDisplayContent.mInputMethodWindow);
         doReturn(true).when(mDisplayContent.mInputMethodWindow).isVisible();
 
+        DisplayPolicy policy = mDisplayContent.getDisplayPolicy();
+        spyOn(policy);
+        doReturn(true).when(policy).shouldAttachNavBarToAppDuringTransition();
+
         // create home activity
         Task rootHomeTask = mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask();
         final ActivityRecord homeActivity = new ActivityBuilder(mWm.mAtmService)
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
index 337e1f9..7fe8582 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
@@ -27,6 +27,8 @@
 import com.android.internal.util.dump.DualDumpOutputStream;
 import com.android.server.audio.AudioService;
 
+import java.util.Arrays;
+
 /**
  * Represents the ALSA specification, and attributes of an ALSA device.
  */
@@ -36,17 +38,21 @@
 
     private final int mCardNum;
     private final int mDeviceNum;
+    private final String mAlsaCardDeviceString;
     private final String mDeviceAddress;
-    private final boolean mHasOutput;
-    private final boolean mHasInput;
 
-    private final boolean mIsInputHeadset;
-    private final boolean mIsOutputHeadset;
+    // The following two constant will be used as index to access arrays.
+    private static final int INPUT = 0;
+    private static final int OUTPUT = 1;
+    private static final int NUM_DIRECTIONS = 2;
+    private static final String[] DIRECTION_STR = {"INPUT", "OUTPUT"};
+    private final boolean[] mHasDevice = new boolean[NUM_DIRECTIONS];
+
+    private final boolean[] mIsHeadset = new boolean[NUM_DIRECTIONS];
     private final boolean mIsDock;
-
-    private boolean mSelected = false;
-    private int mOutputState;
-    private int mInputState;
+    private final int[] mDeviceType = new int[NUM_DIRECTIONS];
+    private boolean[] mIsSelected = new boolean[NUM_DIRECTIONS];
+    private int[] mState = new int[NUM_DIRECTIONS];
     private UsbAlsaJackDetector mJackDetector;
     private IAudioService mAudioService;
 
@@ -60,11 +66,13 @@
         mCardNum = card;
         mDeviceNum = device;
         mDeviceAddress = deviceAddress;
-        mHasOutput = hasOutput;
-        mHasInput = hasInput;
-        mIsInputHeadset = isInputHeadset;
-        mIsOutputHeadset = isOutputHeadset;
+        mHasDevice[OUTPUT] = hasOutput;
+        mHasDevice[INPUT] = hasInput;
+        mIsHeadset[INPUT] = isInputHeadset;
+        mIsHeadset[OUTPUT] = isOutputHeadset;
         mIsDock = isDock;
+        initDeviceType();
+        mAlsaCardDeviceString = getAlsaCardDeviceString();
     }
 
     /**
@@ -104,28 +112,28 @@
      * @return true if the device supports output.
      */
     public boolean hasOutput() {
-        return mHasOutput;
+        return mHasDevice[OUTPUT];
     }
 
     /**
      * @return true if the device supports input (recording).
      */
     public boolean hasInput() {
-        return mHasInput;
-    }
-
-    /**
-     * @return true if the device is a headset for purposes of input.
-     */
-    public boolean isInputHeadset() {
-        return mIsInputHeadset;
+        return mHasDevice[INPUT];
     }
 
     /**
      * @return true if the device is a headset for purposes of output.
      */
     public boolean isOutputHeadset() {
-        return mIsOutputHeadset;
+        return mIsHeadset[OUTPUT];
+    }
+
+    /**
+     * @return true if the device is a headset for purposes of input.
+     */
+    public boolean isInputHeadset() {
+        return mIsHeadset[INPUT];
     }
 
     /**
@@ -157,6 +165,9 @@
 
     /** Begins a jack-detection thread. */
     private synchronized void startJackDetect() {
+        if (mJackDetector != null) {
+            return;
+        }
         // If no jack detect capabilities exist, mJackDetector will be null.
         mJackDetector = UsbAlsaJackDetector.startJackDetect(this);
     }
@@ -171,75 +182,152 @@
 
     /** Start using this device as the selected USB Audio Device. */
     public synchronized void start() {
-        mSelected = true;
-        mInputState = 0;
-        mOutputState = 0;
+        startInput();
+        startOutput();
+    }
+
+    /** Start using this device as the selected USB input device. */
+    public synchronized void startInput() {
+        startDevice(INPUT);
+    }
+
+    /** Start using this device as selected USB output device. */
+    public synchronized void startOutput() {
+        startDevice(OUTPUT);
+    }
+
+    private void startDevice(int direction) {
+        if (mIsSelected[direction]) {
+            return;
+        }
+        mIsSelected[direction] = true;
+        mState[direction] = 0;
         startJackDetect();
-        updateWiredDeviceConnectionState(true);
+        updateWiredDeviceConnectionState(direction, true /*enable*/);
     }
 
     /** Stop using this device as the selected USB Audio Device. */
     public synchronized void stop() {
-        stopJackDetect();
-        updateWiredDeviceConnectionState(false);
-        mSelected = false;
+        stopInput();
+        stopOutput();
     }
 
-    /** Updates AudioService with the connection state of the alsaDevice.
-     *  Checks ALSA Jack state for inputs and outputs before reporting.
+    /** Stop using this device as the selected USB input device. */
+    public synchronized void stopInput() {
+        if (!mIsSelected[INPUT]) {
+            return;
+        }
+        if (!mIsSelected[OUTPUT]) {
+            // Stop jack detection when both input and output are stopped
+            stopJackDetect();
+        }
+        updateInputWiredDeviceConnectionState(false /*enable*/);
+        mIsSelected[INPUT] = false;
+    }
+
+    /** Stop using this device as the selected USB output device. */
+    public synchronized void stopOutput() {
+        if (!mIsSelected[OUTPUT]) {
+            return;
+        }
+        if (!mIsSelected[INPUT]) {
+            // Stop jack detection when both input and output are stopped
+            stopJackDetect();
+        }
+        updateOutputWiredDeviceConnectionState(false /*enable*/);
+        mIsSelected[OUTPUT] = false;
+    }
+
+    private void initDeviceType() {
+        mDeviceType[INPUT] = mHasDevice[INPUT]
+                ? (mIsHeadset[INPUT] ? AudioSystem.DEVICE_IN_USB_HEADSET
+                                     : AudioSystem.DEVICE_IN_USB_DEVICE)
+                : AudioSystem.DEVICE_NONE;
+        mDeviceType[OUTPUT] = mHasDevice[OUTPUT]
+                ? (mIsDock ? AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET
+                           : (mIsHeadset[OUTPUT] ? AudioSystem.DEVICE_OUT_USB_HEADSET
+                                                 : AudioSystem.DEVICE_OUT_USB_DEVICE))
+                : AudioSystem.DEVICE_NONE;
+    }
+
+    /**
+     * @return the output device type that will be used to notify AudioService about device
+     *         connection. If there is no output on this device, {@link AudioSystem#DEVICE_NONE}
+     *         will be returned.
      */
-    public synchronized void updateWiredDeviceConnectionState(boolean enable) {
-        if (!mSelected) {
-            Slog.e(TAG, "updateWiredDeviceConnectionState on unselected AlsaDevice!");
-            return;
-        }
-        String alsaCardDeviceString = getAlsaCardDeviceString();
-        if (alsaCardDeviceString == null) {
-            return;
-        }
-        try {
-            // Output Device
-            if (mHasOutput) {
-                int device = mIsDock ? AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET
-                        : (mIsOutputHeadset
-                            ? AudioSystem.DEVICE_OUT_USB_HEADSET
-                            : AudioSystem.DEVICE_OUT_USB_DEVICE);
-                if (DEBUG) {
-                    Slog.d(TAG, "pre-call device:0x" + Integer.toHexString(device)
-                            + " addr:" + alsaCardDeviceString
-                            + " name:" + mDeviceName);
-                }
-                boolean connected = isOutputJackConnected();
-                Slog.i(TAG, "OUTPUT JACK connected: " + connected);
-                int outputState = (enable && connected) ? 1 : 0;
-                if (outputState != mOutputState) {
-                    mOutputState = outputState;
-                    AudioDeviceAttributes attributes = new AudioDeviceAttributes(device,
-                            alsaCardDeviceString, mDeviceName);
-                    mAudioService.setWiredDeviceConnectionState(attributes, outputState, TAG);
-                }
-            }
-
-            // Input Device
-            if (mHasInput) {
-                int device = mIsInputHeadset
-                        ? AudioSystem.DEVICE_IN_USB_HEADSET
-                        : AudioSystem.DEVICE_IN_USB_DEVICE;
-                boolean connected = isInputJackConnected();
-                Slog.i(TAG, "INPUT JACK connected: " + connected);
-                int inputState = (enable && connected) ? 1 : 0;
-                if (inputState != mInputState) {
-                    mInputState = inputState;
-                    AudioDeviceAttributes attributes = new AudioDeviceAttributes(device,
-                            alsaCardDeviceString, mDeviceName);
-                    mAudioService.setWiredDeviceConnectionState(attributes, inputState, TAG);
-                }
-            }
-        } catch (RemoteException e) {
-            Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState");
-        }
+    public int getOutputDeviceType() {
+        return mDeviceType[OUTPUT];
     }
 
+    /**
+     * @return the input device type that will be used to notify AudioService about device
+     *         connection. If there is no input on this device, {@link AudioSystem#DEVICE_NONE}
+     *         will be returned.
+     */
+    public int getInputDeviceType() {
+        return mDeviceType[INPUT];
+    }
+
+    private boolean updateWiredDeviceConnectionState(int direction, boolean enable) {
+        if (!mIsSelected[direction]) {
+            Slog.e(TAG, "Updating wired device connection state on unselected device");
+            return false;
+        }
+        if (mDeviceType[direction] == AudioSystem.DEVICE_NONE) {
+            Slog.d(TAG,
+                    "Unable to set device connection state as " + DIRECTION_STR[direction]
+                    + " device type is none");
+            return false;
+        }
+        if (mAlsaCardDeviceString == null) {
+            Slog.w(TAG, "Failed to update " + DIRECTION_STR[direction] + " device connection "
+                    + "state failed as alsa card device string is null");
+            return false;
+        }
+        if (DEBUG) {
+            Slog.d(TAG, "pre-call device:0x" + Integer.toHexString(mDeviceType[direction])
+                    + " addr:" + mAlsaCardDeviceString
+                    + " name:" + mDeviceName);
+        }
+        boolean connected = direction == INPUT ? isInputJackConnected() : isOutputJackConnected();
+        Slog.i(TAG, DIRECTION_STR[direction] + " JACK connected: " + connected);
+        int state = (enable && connected) ? 1 : 0;
+        if (state != mState[direction]) {
+            mState[direction] = state;
+            AudioDeviceAttributes attributes = new AudioDeviceAttributes(
+                    mDeviceType[direction], mAlsaCardDeviceString, mDeviceName);
+            try {
+                mAudioService.setWiredDeviceConnectionState(attributes, state, TAG);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState for "
+                        + DIRECTION_STR[direction]);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Notify AudioService about the input device connection state.
+     *
+     * @param enable true to notify the device as connected.
+     * @return true only when it successfully notifies AudioService about the device
+     *         connection state.
+     */
+    public synchronized boolean updateInputWiredDeviceConnectionState(boolean enable) {
+        return updateWiredDeviceConnectionState(INPUT, enable);
+    }
+
+    /**
+     * Notify AudioService about the output device connection state.
+     *
+     * @param enable true to notify the device as connected.
+     * @return true only when it successfully notifies AudioService about the device
+     *         connection state.
+     */
+    public synchronized boolean updateOutputWiredDeviceConnectionState(boolean enable) {
+        return updateWiredDeviceConnectionState(OUTPUT, enable);
+    }
 
     /**
      * @Override
@@ -249,8 +337,8 @@
         return "UsbAlsaDevice: [card: " + mCardNum
             + ", device: " + mDeviceNum
             + ", name: " + mDeviceName
-            + ", hasOutput: " + mHasOutput
-            + ", hasInput: " + mHasInput + "]";
+            + ", hasOutput: " + mHasDevice[OUTPUT]
+            + ", hasInput: " + mHasDevice[INPUT] + "]";
     }
 
     /**
@@ -262,8 +350,8 @@
         dump.write("card", UsbAlsaDeviceProto.CARD, mCardNum);
         dump.write("device", UsbAlsaDeviceProto.DEVICE, mDeviceNum);
         dump.write("name", UsbAlsaDeviceProto.NAME, mDeviceName);
-        dump.write("has_output", UsbAlsaDeviceProto.HAS_PLAYBACK, mHasOutput);
-        dump.write("has_input", UsbAlsaDeviceProto.HAS_CAPTURE, mHasInput);
+        dump.write("has_output", UsbAlsaDeviceProto.HAS_PLAYBACK, mHasDevice[OUTPUT]);
+        dump.write("has_input", UsbAlsaDeviceProto.HAS_CAPTURE, mHasDevice[INPUT]);
         dump.write("address", UsbAlsaDeviceProto.ADDRESS, mDeviceAddress);
 
         dump.end(token);
@@ -294,10 +382,8 @@
         UsbAlsaDevice other = (UsbAlsaDevice) obj;
         return (mCardNum == other.mCardNum
                 && mDeviceNum == other.mDeviceNum
-                && mHasOutput == other.mHasOutput
-                && mHasInput == other.mHasInput
-                && mIsInputHeadset == other.mIsInputHeadset
-                && mIsOutputHeadset == other.mIsOutputHeadset
+                && Arrays.equals(mHasDevice, other.mHasDevice)
+                && Arrays.equals(mIsHeadset, other.mIsHeadset)
                 && mIsDock == other.mIsDock);
     }
 
@@ -310,10 +396,10 @@
         int result = 1;
         result = prime * result + mCardNum;
         result = prime * result + mDeviceNum;
-        result = prime * result + (mHasOutput ? 0 : 1);
-        result = prime * result + (mHasInput ? 0 : 1);
-        result = prime * result + (mIsInputHeadset ? 0 : 1);
-        result = prime * result + (mIsOutputHeadset ? 0 : 1);
+        result = prime * result + (mHasDevice[OUTPUT] ? 0 : 1);
+        result = prime * result + (mHasDevice[INPUT] ? 0 : 1);
+        result = prime * result + (mIsHeadset[INPUT] ? 0 : 1);
+        result = prime * result + (mIsHeadset[OUTPUT] ? 0 : 1);
         result = prime * result + (mIsDock ? 0 : 1);
 
         return result;
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java b/services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java
index c498847..d4f0b59 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java
@@ -81,7 +81,8 @@
             if (mStopJackDetect) {
                 return false;
             }
-            mAlsaDevice.updateWiredDeviceConnectionState(true);
+            mAlsaDevice.updateOutputWiredDeviceConnectionState(true);
+            mAlsaDevice.updateInputWiredDeviceConnectionState(true);
         }
         return true;
     }
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index aa1d556..99881e1 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -20,12 +20,14 @@
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.hardware.usb.UsbDevice;
+import android.media.AudioManager;
 import android.media.IAudioService;
 import android.media.midi.MidiDeviceInfo;
 import android.os.Bundle;
 import android.os.FileObserver;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.provider.Settings;
 import android.service.usb.UsbAlsaManagerProto;
 import android.util.Slog;
@@ -42,6 +44,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Stack;
 
 /**
  * UsbAlsaManager manages USB audio and MIDI devices.
@@ -51,8 +54,9 @@
     private static final boolean DEBUG = false;
 
     // Flag to turn on/off multi-peripheral select mode
-    // Set to true to have single-device-only mode
-    private static final boolean mIsSingleMode = true;
+    // Set to true to have multi-devices mode
+    private static final boolean IS_MULTI_MODE = SystemProperties.getBoolean(
+            "ro.audio.multi_usb_mode", false /*def*/);
 
     private static final String ALSA_DIRECTORY = "/dev/snd/";
 
@@ -70,7 +74,11 @@
     // this is needed to map USB devices to ALSA Audio Devices, especially to remove an
     // ALSA device when we are notified that its associated USB device has been removed.
     private final ArrayList<UsbAlsaDevice> mAlsaDevices = new ArrayList<UsbAlsaDevice>();
-    private UsbAlsaDevice mSelectedDevice;
+    // A map from device type to attached devices. Given the audio framework only supports
+    // single device connection per device type, only the last attached device will be
+    // connected to audio framework. Once the last device is removed, previous device can
+    // be connected to audio framework.
+    private HashMap<Integer, Stack<UsbAlsaDevice>> mAttachedDevices = new HashMap<>();
 
     //
     // Device Denylist
@@ -162,11 +170,6 @@
             Slog.d(TAG, "selectAlsaDevice() " + alsaDevice);
         }
 
-        // This must be where an existing USB audio device is deselected.... (I think)
-        if (mIsSingleMode && mSelectedDevice != null) {
-            deselectAlsaDevice();
-        }
-
         // FIXME Does not yet handle the case where the setting is changed
         // after device connection.  Ideally we should handle the settings change
         // in SettingsObserver. Here we should log that a USB device is connected
@@ -178,21 +181,18 @@
             return;
         }
 
-        mSelectedDevice = alsaDevice;
         alsaDevice.start();
+
         if (DEBUG) {
             Slog.d(TAG, "selectAlsaDevice() - done.");
         }
     }
 
-    private synchronized void deselectAlsaDevice() {
+    private synchronized void deselectAlsaDevice(UsbAlsaDevice selectedDevice) {
         if (DEBUG) {
-            Slog.d(TAG, "deselectAlsaDevice() mSelectedDevice " + mSelectedDevice);
+            Slog.d(TAG, "deselectAlsaDevice() selectedDevice " + selectedDevice);
         }
-        if (mSelectedDevice != null) {
-            mSelectedDevice.stop();
-            mSelectedDevice = null;
-        }
+        selectedDevice.stop();
     }
 
     private int getAlsaDeviceListIndexFor(String deviceAddress) {
@@ -204,32 +204,86 @@
         return -1;
     }
 
-    private UsbAlsaDevice removeAlsaDeviceFromList(String deviceAddress) {
+    private void addDeviceToAttachedDevicesMap(int deviceType, UsbAlsaDevice device) {
+        if (deviceType == AudioManager.DEVICE_NONE) {
+            Slog.i(TAG, "Ignore caching device as the type is NONE, device=" + device);
+            return;
+        }
+        Stack<UsbAlsaDevice> devices = mAttachedDevices.get(deviceType);
+        if (devices == null) {
+            mAttachedDevices.put(deviceType, new Stack<>());
+            devices = mAttachedDevices.get(deviceType);
+        }
+        devices.push(device);
+    }
+
+    private void addAlsaDevice(UsbAlsaDevice device) {
+        mAlsaDevices.add(0, device);
+        addDeviceToAttachedDevicesMap(device.getInputDeviceType(), device);
+        addDeviceToAttachedDevicesMap(device.getOutputDeviceType(), device);
+    }
+
+    private void removeDeviceFromAttachedDevicesMap(int deviceType, UsbAlsaDevice device) {
+        Stack<UsbAlsaDevice> devices = mAttachedDevices.get(deviceType);
+        if (devices == null) {
+            return;
+        }
+        devices.remove(device);
+        if (devices.isEmpty()) {
+            mAttachedDevices.remove(deviceType);
+        }
+    }
+
+    private UsbAlsaDevice removeAlsaDevice(String deviceAddress) {
         int index = getAlsaDeviceListIndexFor(deviceAddress);
         if (index > -1) {
-            return mAlsaDevices.remove(index);
+            UsbAlsaDevice device = mAlsaDevices.remove(index);
+            removeDeviceFromAttachedDevicesMap(device.getOutputDeviceType(), device);
+            removeDeviceFromAttachedDevicesMap(device.getInputDeviceType(), device);
+            return device;
         } else {
             return null;
         }
     }
 
-    /* package */ UsbAlsaDevice selectDefaultDevice() {
+    private UsbAlsaDevice selectDefaultDevice(int deviceType) {
         if (DEBUG) {
-            Slog.d(TAG, "selectDefaultDevice()");
+            Slog.d(TAG, "selectDefaultDevice():" + deviceType);
         }
 
-        if (mAlsaDevices.size() > 0) {
-            UsbAlsaDevice alsaDevice = mAlsaDevices.get(0);
-            if (DEBUG) {
-                Slog.d(TAG, "  alsaDevice:" + alsaDevice);
-            }
-            if (alsaDevice != null) {
-                selectAlsaDevice(alsaDevice);
-            }
-            return alsaDevice;
-        } else {
+        Stack<UsbAlsaDevice> devices = mAttachedDevices.get(deviceType);
+        if (devices == null || devices.isEmpty()) {
             return null;
         }
+        UsbAlsaDevice alsaDevice = devices.peek();
+        Slog.d(TAG, "select default device:" + alsaDevice);
+        if (AudioManager.isInputDevice(deviceType)) {
+            alsaDevice.startInput();
+        } else {
+            alsaDevice.startOutput();
+        }
+        return alsaDevice;
+    }
+
+    private void deselectCurrentDevice(int deviceType) {
+        if (DEBUG) {
+            Slog.d(TAG, "deselectCurrentDevice():" + deviceType);
+        }
+        if (deviceType == AudioManager.DEVICE_NONE) {
+            return;
+        }
+
+        Stack<UsbAlsaDevice> devices = mAttachedDevices.get(deviceType);
+        if (devices == null || devices.isEmpty()) {
+            return;
+        }
+        UsbAlsaDevice alsaDevice = devices.peek();
+        Slog.d(TAG, "deselect current device:" + alsaDevice);
+        if (AudioManager.isInputDevice(deviceType)) {
+            alsaDevice.stopInput();
+        } else {
+            alsaDevice.stopOutput();
+        }
     }
 
     /* package */ void usbDeviceAdded(String deviceAddress, UsbDevice usbDevice,
@@ -246,6 +300,7 @@
         AlsaCardsParser.AlsaCardRecord cardRec =
                 mCardsParser.findCardNumFor(deviceAddress);
         if (cardRec == null) {
+            Slog.e(TAG, "usbDeviceAdded(): cannot find sound card for " + deviceAddress);
             return;
         }
 
@@ -275,12 +330,19 @@
                     new UsbAlsaDevice(mAudioService, cardRec.getCardNum(), 0 /*device*/,
                                       deviceAddress, hasOutput, hasInput,
                                       isInputHeadset, isOutputHeadset, isDock);
-            if (alsaDevice != null) {
-                alsaDevice.setDeviceNameAndDescription(
-                          cardRec.getCardName(), cardRec.getCardDescription());
-                mAlsaDevices.add(0, alsaDevice);
-                selectAlsaDevice(alsaDevice);
+            alsaDevice.setDeviceNameAndDescription(
+                      cardRec.getCardName(), cardRec.getCardDescription());
+            if (IS_MULTI_MODE) {
+                deselectCurrentDevice(alsaDevice.getInputDeviceType());
+                deselectCurrentDevice(alsaDevice.getOutputDeviceType());
+            } else {
+                // At single mode, the first device is the selected device.
+                if (!mAlsaDevices.isEmpty()) {
+                    deselectAlsaDevice(mAlsaDevices.get(0));
+                }
             }
+            addAlsaDevice(alsaDevice);
+            selectAlsaDevice(alsaDevice);
         }
 
         addMidiDevice(deviceAddress, usbDevice, parser, cardRec);
@@ -346,12 +408,20 @@
         }
 
         // Audio
-        UsbAlsaDevice alsaDevice = removeAlsaDeviceFromList(deviceAddress);
+        UsbAlsaDevice alsaDevice = removeAlsaDevice(deviceAddress);
         Slog.i(TAG, "USB Audio Device Removed: " + alsaDevice);
-        if (alsaDevice != null && alsaDevice == mSelectedDevice) {
+        if (alsaDevice != null) {
             waitForAlsaDevice(alsaDevice.getCardNum(), false /*isAdded*/);
-            deselectAlsaDevice();
-            selectDefaultDevice(); // if there any external devices left, select one of them
+            deselectAlsaDevice(alsaDevice);
+            if (IS_MULTI_MODE) {
+                selectDefaultDevice(alsaDevice.getOutputDeviceType());
+                selectDefaultDevice(alsaDevice.getInputDeviceType());
+            } else {
+                // If there are any external devices left, select the latest attached one
+                if (!mAlsaDevices.isEmpty() && mAlsaDevices.get(0) != null) {
+                    selectAlsaDevice(mAlsaDevices.get(0));
+                }
+            }
         }
 
         // MIDI
@@ -362,7 +432,6 @@
         }
 
         logDevices("usbDeviceRemoved()");
-
     }
 
    /* package */ void setPeripheralMidiState(boolean enabled, int card, int device) {
diff --git a/services/voiceinteraction/Android.bp b/services/voiceinteraction/Android.bp
index 7332d2d..de8d144 100644
--- a/services/voiceinteraction/Android.bp
+++ b/services/voiceinteraction/Android.bp
@@ -9,11 +9,60 @@
 
 filegroup {
     name: "services.voiceinteraction-sources",
-    srcs: ["java/**/*.java"],
+    srcs: ["java/com/android/server/voiceinteraction/*.java"],
     path: "java",
     visibility: ["//frameworks/base/services"],
 }
 
+filegroup {
+    name: "services.soundtrigger_middleware-sources",
+    srcs: ["java/com/android/server/soundtrigger_middleware/*.java"],
+    path: "java",
+    visibility: ["//visibility:private"],
+}
+
+filegroup {
+    name: "services.soundtrigger_service-sources",
+    srcs: ["java/com/android/server/soundtrigger/*.java"],
+    path: "java",
+    visibility: ["//visibility:private"],
+}
+
+filegroup {
+    name: "services.soundtrigger-sources",
+    srcs: [
+        ":services.soundtrigger_service-sources",
+        ":services.soundtrigger_middleware-sources",
+    ],
+    path: "java",
+    visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+    name: "services.soundtrigger_middleware",
+    defaults: ["platform_service_defaults"],
+    srcs: [":services.soundtrigger_middleware-sources"],
+    libs: [
+        "services.core",
+    ],
+    static_libs: [
+        "android.hardware.soundtrigger-V2.3-java",
+    ],
+    visibility: ["//visibility/base/services/tests/voiceinteraction"],
+}
+
+java_library_static {
+    name: "services.soundtrigger",
+    defaults: ["platform_service_defaults"],
+    srcs: [":services.soundtrigger_service-sources"],
+    libs: [
+        "services.core",
+    ],
+    static_libs: [
+        "services.soundtrigger_middleware",
+    ],
+}
+
 java_library_static {
     name: "services.voiceinteraction",
     defaults: ["platform_service_defaults"],
diff --git a/services/voiceinteraction/TEST_MAPPING b/services/voiceinteraction/TEST_MAPPING
index 5fe1c8d..f098155 100644
--- a/services/voiceinteraction/TEST_MAPPING
+++ b/services/voiceinteraction/TEST_MAPPING
@@ -5,6 +5,9 @@
       "options": [
         {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceStressTest"
         }
       ]
     },
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 5efd158..07dc1c6 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -315,12 +315,13 @@
             IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig,
             int keyphraseId, boolean runInBatterySaverMode) {
         synchronized (mLock) {
+            // TODO Remove previous callback handling
             IRecognitionStatusCallback oldCallback = modelData.getCallback();
             if (oldCallback != null && oldCallback.asBinder() != callback.asBinder()) {
                 Slog.w(TAG, "Canceling previous recognition for model id: "
                         + modelData.getModelId());
                 try {
-                    oldCallback.onError(STATUS_ERROR);
+                    oldCallback.onPreempted();
                 } catch (RemoteException e) {
                     Slog.w(TAG, "RemoteException in onDetectionStopped", e);
                 }
@@ -759,15 +760,12 @@
                     onRecognitionAbortLocked(event);
                     break;
                 case SoundTrigger.RECOGNITION_STATUS_FAILURE:
-                    // Fire failures to all listeners since it's not tied to a keyphrase.
-                    onRecognitionFailureLocked();
-                    break;
                 case SoundTrigger.RECOGNITION_STATUS_SUCCESS:
                 case SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE:
                     if (isKeyphraseRecognitionEvent(event)) {
-                        onKeyphraseRecognitionSuccessLocked((KeyphraseRecognitionEvent) event);
+                        onKeyphraseRecognitionLocked((KeyphraseRecognitionEvent) event);
                     } else {
-                        onGenericRecognitionSuccessLocked((GenericRecognitionEvent) event);
+                        onGenericRecognitionLocked((GenericRecognitionEvent) event);
                     }
                     break;
             }
@@ -778,7 +776,7 @@
         return event instanceof KeyphraseRecognitionEvent;
     }
 
-    private void onGenericRecognitionSuccessLocked(GenericRecognitionEvent event) {
+    private void onGenericRecognitionLocked(GenericRecognitionEvent event) {
         MetricsLogger.count(mContext, "sth_generic_recognition_event", 1);
         if (event.status != SoundTrigger.RECOGNITION_STATUS_SUCCESS
                 && event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
@@ -901,17 +899,6 @@
         }
     }
 
-    private void onRecognitionFailureLocked() {
-        Slog.w(TAG, "Recognition failure");
-        MetricsLogger.count(mContext, "sth_recognition_failure_event", 1);
-        try {
-            sendErrorCallbacksToAllLocked(STATUS_ERROR);
-        } finally {
-            internalClearModelStateLocked();
-            internalClearGlobalStateLocked();
-        }
-    }
-
     private int getKeyphraseIdFromEvent(KeyphraseRecognitionEvent event) {
         if (event == null) {
             Slog.w(TAG, "Null RecognitionEvent received.");
@@ -927,7 +914,7 @@
         return keyphraseExtras[0].id;
     }
 
-    private void onKeyphraseRecognitionSuccessLocked(KeyphraseRecognitionEvent event) {
+    private void onKeyphraseRecognitionLocked(KeyphraseRecognitionEvent event) {
         Slog.i(TAG, "Recognition success");
         MetricsLogger.count(mContext, "sth_keyphrase_recognition_event", 1);
         int keyphraseId = getKeyphraseIdFromEvent(event);
@@ -1001,7 +988,17 @@
     private void onServiceDiedLocked() {
         try {
             MetricsLogger.count(mContext, "sth_service_died", 1);
-            sendErrorCallbacksToAllLocked(SoundTrigger.STATUS_DEAD_OBJECT);
+            for (ModelData modelData : mModelDataMap.values()) {
+                IRecognitionStatusCallback callback = modelData.getCallback();
+                if (callback != null) {
+                    try {
+                        callback.onModuleDied();
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "RemoteException send moduleDied for model handle " +
+                                modelData.getHandle(), e);
+                    }
+                }
+            }
         } finally {
             internalClearModelStateLocked();
             internalClearGlobalStateLocked();
@@ -1111,21 +1108,6 @@
         }
     }
 
-    // Sends an error callback to all models with a valid registered callback.
-    private void sendErrorCallbacksToAllLocked(int errorCode) {
-        for (ModelData modelData : mModelDataMap.values()) {
-            IRecognitionStatusCallback callback = modelData.getCallback();
-            if (callback != null) {
-                try {
-                    callback.onError(errorCode);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "RemoteException sendErrorCallbacksToAllLocked for model handle " +
-                            modelData.getHandle(), e);
-                }
-            }
-        }
-    }
-
     /**
      * Stops and unloads all models. This is intended as a clean-up call with the expectation that
      * this instance is not used after.
@@ -1342,11 +1324,11 @@
             // Notify of error if needed.
             if (notifyClientOnError) {
                 try {
-                    callback.onError(status);
+                    callback.onResumeFailed(status);
                 } catch (DeadObjectException e) {
                     forceStopAndUnloadModelLocked(modelData, e);
                 } catch (RemoteException e) {
-                    Slog.w(TAG, "RemoteException in onError", e);
+                    Slog.w(TAG, "RemoteException in onResumeFailed", e);
                 }
             }
         } else {
@@ -1382,15 +1364,15 @@
         status = mModule.stopRecognition(modelData.getHandle());
 
         if (status != SoundTrigger.STATUS_OK) {
-            Slog.w(TAG, "stopRecognition call failed with " + status);
+            Slog.e(TAG, "stopRecognition call failed with " + status);
             MetricsLogger.count(mContext, "sth_stop_recognition_error", 1);
             if (notify) {
                 try {
-                    callback.onError(status);
+                    callback.onPauseFailed(status);
                 } catch (DeadObjectException e) {
                     forceStopAndUnloadModelLocked(modelData, e);
                 } catch (RemoteException e) {
-                    Slog.w(TAG, "RemoteException in onError", e);
+                    Slog.w(TAG, "RemoteException in onPauseFailed", e);
                 }
             }
         } else {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 04c1c04..1bbea89 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -25,6 +25,7 @@
 import static android.content.pm.PackageManager.GET_SERVICES;
 import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
 import static android.hardware.soundtrigger.SoundTrigger.STATUS_BAD_VALUE;
+import static android.hardware.soundtrigger.SoundTrigger.STATUS_DEAD_OBJECT;
 import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
 import static android.hardware.soundtrigger.SoundTrigger.STATUS_OK;
 import static android.provider.Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY;
@@ -39,12 +40,13 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.PermissionChecker;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.hardware.soundtrigger.ConversionUtil;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
 import android.hardware.soundtrigger.ModelParams;
-import android.hardware.soundtrigger.ConversionUtil;
 import android.hardware.soundtrigger.SoundTrigger;
 import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
 import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
@@ -64,6 +66,7 @@
 import android.media.soundtrigger.ISoundTriggerDetectionService;
 import android.media.soundtrigger.ISoundTriggerDetectionServiceClient;
 import android.media.soundtrigger.SoundTriggerDetectionService;
+import android.media.soundtrigger_middleware.ISoundTriggerInjection;
 import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
 import android.os.Binder;
 import android.os.Bundle;
@@ -74,8 +77,8 @@
 import android.os.ParcelUuid;
 import android.os.PowerManager;
 import android.os.RemoteException;
-import android.os.ServiceSpecificException;
 import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -86,6 +89,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.ISoundTriggerService;
 import com.android.internal.app.ISoundTriggerSession;
+import com.android.server.SoundTriggerInternal;
 import com.android.server.SystemService;
 import com.android.server.utils.EventLogger;
 
@@ -98,8 +102,8 @@
 import java.util.Objects;
 import java.util.TreeMap;
 import java.util.UUID;
-import java.util.stream.Collectors;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 /**
  * A single SystemService to manage all sound/voice-based sound models on the DSP.
@@ -296,6 +300,23 @@
                 return listUnderlyingModuleProperties(originatorIdentity);
             }
         }
+
+        @Override
+        public void attachInjection(@NonNull ISoundTriggerInjection injection) {
+            if (PermissionChecker.checkCallingPermissionForPreflight(mContext,
+                    android.Manifest.permission.MANAGE_SOUND_TRIGGER, null)
+                        != PermissionChecker.PERMISSION_GRANTED) {
+                throw new SecurityException();
+            }
+            try {
+                ISoundTriggerMiddlewareService.Stub
+                        .asInterface(ServiceManager
+                                .waitForService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE))
+                        .attachFakeHalInjection(injection);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
     }
 
     class SoundTriggerSessionStub extends ISoundTriggerSession.Stub {
@@ -1368,8 +1389,7 @@
                         }));
             }
 
-            @Override
-            public void onError(int status) {
+            private void onError(int status) {
                 if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status);
 
                 sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
@@ -1392,6 +1412,30 @@
             }
 
             @Override
+            public void onPreempted() {
+                if (DEBUG) Slog.v(TAG, mPuuid + ": onPreempted");
+                onError(STATUS_ERROR);
+            }
+
+            @Override
+            public void onModuleDied() {
+                if (DEBUG) Slog.v(TAG, mPuuid + ": onModuleDied");
+                onError(STATUS_DEAD_OBJECT);
+            }
+
+            @Override
+            public void onResumeFailed(int status) {
+                if (DEBUG) Slog.v(TAG, mPuuid + ": onResumeFailed: " + status);
+                onError(status);
+            }
+
+            @Override
+            public void onPauseFailed(int status) {
+                if (DEBUG) Slog.v(TAG, mPuuid + ": onPauseFailed: " + status);
+                onError(status);
+            }
+
+            @Override
             public void onRecognitionPaused() {
                 Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionPaused");
 
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerDuplicateModelHandler.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerDuplicateModelHandler.java
new file mode 100644
index 0000000..0104193
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerDuplicateModelHandler.java
@@ -0,0 +1,205 @@
+/*
+ * 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.server.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+import android.media.soundtrigger.ModelParameterRange;
+import android.media.soundtrigger.PhraseSoundModel;
+import android.media.soundtrigger.Properties;
+import android.media.soundtrigger.RecognitionConfig;
+import android.media.soundtrigger.SoundModel;
+import android.media.soundtrigger.Status;
+import android.os.IBinder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This wrapper prevents a models with the same UUID from being loaded concurrently. This is used to
+ * protect STHAL implementations, which don't support concurrent loads of the same model. We reject
+ * the duplicate load with {@link Status#RESOURCE_CONTENTION}.
+ */
+public class SoundTriggerDuplicateModelHandler implements ISoundTriggerHal {
+    private final @NonNull ISoundTriggerHal mDelegate;
+
+    private GlobalCallback mGlobalCallback;
+    // There are rarely more than two models loaded.
+    private final List<ModelData> mModelList = new ArrayList<>();
+
+    private static final class ModelData {
+        ModelData(int modelId, String uuid) {
+            mModelId = modelId;
+            mUuid = uuid;
+        }
+
+        int getModelId() {
+            return mModelId;
+        }
+
+        String getUuid() {
+            return mUuid;
+        }
+
+        boolean getWasContended() {
+            return mWasContended;
+        }
+
+        void setWasContended() {
+            mWasContended = true;
+        }
+
+        private int mModelId;
+        private String mUuid;
+        private boolean mWasContended = false;
+    }
+
+    public SoundTriggerDuplicateModelHandler(@NonNull ISoundTriggerHal delegate) {
+        mDelegate = delegate;
+    }
+
+    @Override
+    public void reboot() {
+        mDelegate.reboot();
+    }
+
+    @Override
+    public void detach() {
+        mDelegate.detach();
+    }
+
+    @Override
+    public Properties getProperties() {
+        return mDelegate.getProperties();
+    }
+
+    @Override
+    public void registerCallback(GlobalCallback callback) {
+        mGlobalCallback = callback;
+        mDelegate.registerCallback(mGlobalCallback);
+    }
+
+    @Override
+    public int loadSoundModel(SoundModel soundModel, ModelCallback callback) {
+        synchronized (this) {
+            checkDuplicateModelUuid(soundModel.uuid);
+            var result = mDelegate.loadSoundModel(soundModel, callback);
+            mModelList.add(new ModelData(result, soundModel.uuid));
+            return result;
+        }
+    }
+
+    @Override
+    public int loadPhraseSoundModel(PhraseSoundModel soundModel, ModelCallback callback) {
+        synchronized (this) {
+            checkDuplicateModelUuid(soundModel.common.uuid);
+            var result = mDelegate.loadPhraseSoundModel(soundModel, callback);
+            mModelList.add(new ModelData(result, soundModel.common.uuid));
+            return result;
+        }
+    }
+
+    @Override
+    public void unloadSoundModel(int modelHandle) {
+        mDelegate.unloadSoundModel(modelHandle);
+        for (int i = 0; i < mModelList.size(); i++) {
+            if (mModelList.get(i).getModelId() == modelHandle) {
+                var modelData = mModelList.remove(i);
+                if (modelData.getWasContended()) {
+                    mGlobalCallback.onResourcesAvailable();
+                }
+                // Model ID is unique
+                return;
+            }
+        }
+    }
+
+    // Uninteresting delegation calls to follow.
+    @Override
+    public void stopRecognition(int modelHandle) {
+        mDelegate.stopRecognition(modelHandle);
+    }
+
+    @Override
+    public void startRecognition(
+            int modelHandle, int deviceHandle, int ioHandle, RecognitionConfig config) {
+        mDelegate.startRecognition(modelHandle, deviceHandle, ioHandle, config);
+    }
+
+    @Override
+    public void forceRecognitionEvent(int modelHandle) {
+        mDelegate.forceRecognitionEvent(modelHandle);
+    }
+
+    @Override
+    public int getModelParameter(int modelHandle, int param) {
+        return mDelegate.getModelParameter(modelHandle, param);
+    }
+
+    @Override
+    public void setModelParameter(int modelHandle, int param, int value) {
+        mDelegate.setModelParameter(modelHandle, param, value);
+    }
+
+    @Override
+    public ModelParameterRange queryParameter(int modelHandle, int param) {
+        return mDelegate.queryParameter(modelHandle, param);
+    }
+
+    @Override
+    public void linkToDeath(IBinder.DeathRecipient recipient) {
+        mDelegate.linkToDeath(recipient);
+    }
+
+    @Override
+    public void unlinkToDeath(IBinder.DeathRecipient recipient) {
+        mDelegate.unlinkToDeath(recipient);
+    }
+
+    @Override
+    public String interfaceDescriptor() {
+        return mDelegate.interfaceDescriptor();
+    }
+
+    @Override
+    public void flushCallbacks() {
+        mDelegate.flushCallbacks();
+    }
+
+    @Override
+    public void clientAttached(IBinder binder) {
+        mDelegate.clientAttached(binder);
+    }
+
+    @Override
+    public void clientDetached(IBinder binder) {
+        mDelegate.clientDetached(binder);
+    }
+
+    /**
+     * Helper for handling duplicate model. If there is a load attempt for a model with a UUID which
+     * is already loaded: 1) Reject with {@link Status.RESOURCE_CONTENTION} 2) Mark the already
+     * loaded model as contended, as we need to dispatch a resource available callback following the
+     * original model being unloaded.
+     */
+    private void checkDuplicateModelUuid(String uuid) {
+        var model = mModelList.stream().filter(x -> x.getUuid().equals(uuid)).findFirst();
+        if (model.isPresent()) {
+            model.get().setWasContended();
+            throw new RecoverableException(Status.RESOURCE_CONTENTION);
+        }
+    }
+}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index d2d8f1a..6223b2e 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -170,7 +170,8 @@
      */
     private void attachToHal() {
         mHalService = new SoundTriggerHalEnforcer(
-                new SoundTriggerHalWatchdog(mHalFactory.create()));
+                new SoundTriggerHalWatchdog(
+                    new SoundTriggerDuplicateModelHandler(mHalFactory.create())));
         mHalService.linkToDeath(this);
         mHalService.registerCallback(this);
         mProperties = mHalService.getProperties();
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
index aaf7a9e..5846ff6 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
@@ -39,7 +39,7 @@
  *
  * @hide
  */
-public class DatabaseHelper extends SQLiteOpenHelper {
+public class DatabaseHelper extends SQLiteOpenHelper implements IEnrolledModelDb {
     static final String TAG = "SoundModelDBHelper";
     static final boolean DBG = false;
 
@@ -153,11 +153,7 @@
         }
     }
 
-    /**
-     * Updates the given keyphrase model, adds it, if it doesn't already exist.
-     *
-     * TODO: We only support one keyphrase currently.
-     */
+    @Override
     public boolean updateKeyphraseSoundModel(KeyphraseSoundModel soundModel) {
         synchronized(this) {
             SQLiteDatabase db = getWritableDatabase();
@@ -193,9 +189,7 @@
         }
     }
 
-    /**
-     * Deletes the sound model and associated keyphrases.
-     */
+    @Override
     public boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale) {
         // Normalize the locale to guard against SQL injection.
         bcp47Locale = Locale.forLanguageTag(bcp47Locale).toLanguageTag();
@@ -218,12 +212,7 @@
         }
     }
 
-    /**
-     * Returns a matching {@link KeyphraseSoundModel} for the keyphrase ID.
-     * Returns null if a match isn't found.
-     *
-     * TODO: We only support one keyphrase currently.
-     */
+    @Override
     public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, int userHandle,
             String bcp47Locale) {
         // Sanitize the locale to guard against SQL injection.
@@ -237,12 +226,7 @@
         }
     }
 
-    /**
-     * Returns a matching {@link KeyphraseSoundModel} for the keyphrase string.
-     * Returns null if a match isn't found.
-     *
-     * TODO: We only support one keyphrase currently.
-     */
+    @Override
     public KeyphraseSoundModel getKeyphraseSoundModel(String keyphrase, int userHandle,
             String bcp47Locale) {
         // Sanitize the locale to guard against SQL injection.
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index f3cb9ba..486945d 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -56,6 +56,7 @@
 import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
 import android.service.voice.ISandboxedDetectionService;
 import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback;
+import android.service.voice.SoundTriggerFailure;
 import android.service.voice.VisualQueryDetectionService;
 import android.service.voice.VisualQueryDetectionServiceFailure;
 import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity;
@@ -153,7 +154,8 @@
     private int mRestartCount = 0;
     @NonNull private ServiceConnection mRemoteHotwordDetectionService;
     @NonNull private ServiceConnection mRemoteVisualQueryDetectionService;
-    private IBinder mAudioFlinger;
+    @GuardedBy("mLock")
+    @Nullable private IBinder mAudioFlinger;
     @GuardedBy("mLock")
     private boolean mDebugHotwordLogging = false;
 
@@ -193,7 +195,7 @@
                 new Intent(VisualQueryDetectionService.SERVICE_INTERFACE);
         visualQueryDetectionServiceIntent.setComponent(mVisualQueryDetectionComponentName);
 
-        initAudioFlingerLocked();
+        initAudioFlinger();
 
         mHotwordDetectionServiceConnectionFactory =
                 new ServiceConnectionFactory(hotwordDetectionServiceIntent,
@@ -226,31 +228,41 @@
         }
     }
 
-    private void initAudioFlingerLocked() {
+    private void initAudioFlinger() {
         if (DEBUG) {
-            Slog.d(TAG, "initAudioFlingerLocked");
+            Slog.d(TAG, "initAudioFlinger");
         }
-        mAudioFlinger = ServiceManager.waitForService("media.audio_flinger");
-        if (mAudioFlinger == null) {
+        final IBinder audioFlinger = ServiceManager.waitForService("media.audio_flinger");
+        if (audioFlinger == null) {
+            setAudioFlinger(null);
             throw new IllegalStateException("Service media.audio_flinger wasn't found.");
         }
         if (DEBUG) {
             Slog.d(TAG, "Obtained audio_flinger binder.");
         }
         try {
-            mAudioFlinger.linkToDeath(mAudioServerDeathRecipient, /* flags= */ 0);
+            audioFlinger.linkToDeath(mAudioServerDeathRecipient, /* flags= */ 0);
         } catch (RemoteException e) {
             Slog.w(TAG, "Audio server died before we registered a DeathRecipient; "
-                            + "retrying init.", e);
-            initAudioFlingerLocked();
+                    + "retrying init.", e);
+            initAudioFlinger();
+            return;
+        }
+
+        setAudioFlinger(audioFlinger);
+    }
+
+    private void setAudioFlinger(@Nullable IBinder audioFlinger) {
+        synchronized (mLock) {
+            mAudioFlinger = audioFlinger;
         }
     }
 
     private void audioServerDied() {
         Slog.w(TAG, "Audio server died; restarting the HotwordDetectionService.");
+        // TODO: Check if this needs to be scheduled on a different thread.
+        initAudioFlinger();
         synchronized (mLock) {
-            // TODO: Check if this needs to be scheduled on a different thread.
-            initAudioFlingerLocked();
             // We restart the process instead of simply sending over the new binder, to avoid race
             // conditions with audio reading in the service.
             restartProcessLocked();
@@ -576,8 +588,31 @@
         }
 
         @Override
-        public void onError(int status) throws RemoteException {
-            mExternalCallback.onError(status);
+        public void onPreempted() throws RemoteException {
+            mExternalCallback.onSoundTriggerFailure(new SoundTriggerFailure(
+                        SoundTriggerFailure.ERROR_CODE_UNEXPECTED_PREEMPTION,
+                        "Unexpected startRecognition on already started ST session"));
+        }
+
+        @Override
+        public void onModuleDied() throws RemoteException {
+            mExternalCallback.onSoundTriggerFailure(new SoundTriggerFailure(
+                        SoundTriggerFailure.ERROR_CODE_MODULE_DIED,
+                        "STHAL died"));
+        }
+
+        @Override
+        public void onResumeFailed(int status) throws RemoteException {
+            mExternalCallback.onSoundTriggerFailure(new SoundTriggerFailure(
+                        SoundTriggerFailure.ERROR_CODE_RECOGNITION_RESUME_FAILED,
+                        "STService recognition resume failed with: " + status));
+        }
+
+        @Override
+        public void onPauseFailed(int status) throws RemoteException {
+            mExternalCallback.onSoundTriggerFailure(new SoundTriggerFailure(
+                        SoundTriggerFailure.ERROR_CODE_RECOGNITION_RESUME_FAILED,
+                        "STService recognition pause failed with: " + status));
         }
 
         @Override
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/IEnrolledModelDb.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/IEnrolledModelDb.java
new file mode 100644
index 0000000..f10c2f6
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/IEnrolledModelDb.java
@@ -0,0 +1,90 @@
+/**
+ * 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.server.voiceinteraction;
+
+import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
+import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+
+import java.io.PrintWriter;
+
+/**
+ * Interface for registering and querying the enrolled keyphrase model database for
+ * {@link VoiceInteractionManagerService}.
+ * This interface only supports one keyphrase per {@link KeyphraseSoundModel}.
+ * The non-update methods are uniquely keyed on fields of the first keyphrase
+ * {@link KeyphraseSoundModel#getKeyphrases()}.
+ * @hide
+ */
+public interface IEnrolledModelDb {
+
+    //TODO(273286174): We only support one keyphrase currently.
+    /**
+     * Register the given {@link KeyphraseSoundModel}, or updates it if it already exists.
+     *
+     * @param soundModel - The sound model to register in the database.
+     * Updates the sound model if the keyphrase id, users, locale match an existing entry.
+     * Must have one and only one associated {@link Keyphrase}.
+     * @return - {@code true} if successful, {@code false} if unsuccessful
+     */
+    boolean updateKeyphraseSoundModel(KeyphraseSoundModel soundModel);
+
+    /**
+     * Deletes the previously registered keyphrase sound model from the database.
+     *
+     * @param keyphraseId - The (first) keyphrase ID of the KeyphraseSoundModel to delete.
+     * @param userHandle - The user handle making this request. Must be included in the user
+     *                     list of the registered sound model.
+     * @param bcp47Locale - The locale of the (first) keyphrase associated with this model.
+     * @return - {@code true} if successful, {@code false} if unsuccessful
+     */
+    boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale);
+
+    //TODO(273286174): We only support one keyphrase currently.
+    /**
+     * Returns the first matching {@link KeyphraseSoundModel} for the keyphrase ID, locale pair,
+     * contingent on the userHandle existing in the user list for the model.
+     * Returns null if a match isn't found.
+     *
+     * @param keyphraseId - The (first) keyphrase ID of the KeyphraseSoundModel to query.
+     * @param userHandle - The user handle making this request. Must be included in the user
+     *                     list of the registered sound model.
+     * @param bcp47Locale - The locale of the (first) keyphrase associated with this model.
+     * @return - {@code true} if successful, {@code false} if unsuccessful
+     */
+    KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, int userHandle,
+            String bcp47Locale);
+
+    //TODO(273286174): We only support one keyphrase currently.
+    /**
+     * Returns the first matching {@link KeyphraseSoundModel} for the keyphrase ID, locale pair,
+     * contingent on the userHandle existing in the user list for the model.
+     * Returns null if a match isn't found.
+     *
+     * @param keyphrase - The text of (the first) keyphrase of the KeyphraseSoundModel to query.
+     * @param userHandle - The user handle making this request. Must be included in the user
+     *                     list of the registered sound model.
+     * @param bcp47Locale - The locale of the (first) keyphrase associated with this model.
+     * @return - {@code true} if successful, {@code false} if unsuccessful
+     */
+    KeyphraseSoundModel getKeyphraseSoundModel(String keyphrase, int userHandle,
+            String bcp47Locale);
+
+    /**
+     * Dumps contents of database for dumpsys
+     */
+    void dump(PrintWriter pw);
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/TestModelEnrollmentDatabase.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/TestModelEnrollmentDatabase.java
new file mode 100644
index 0000000..9bbaf8e
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/TestModelEnrollmentDatabase.java
@@ -0,0 +1,148 @@
+/**
+ * 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.server.voiceinteraction;
+
+import android.annotation.NonNull;
+import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
+import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.StringJoiner;
+
+/**
+ * In memory model enrollment database for testing purposes.
+ * @hide
+ */
+public class TestModelEnrollmentDatabase implements IEnrolledModelDb {
+
+    // Record representing the primary key used in the real model database.
+    private static final class EnrollmentKey {
+        private final int mKeyphraseId;
+        private final List<Integer> mUserIds;
+        private final String mLocale;
+
+        EnrollmentKey(int keyphraseId,
+                @NonNull List<Integer> userIds, @NonNull String locale) {
+            mKeyphraseId = keyphraseId;
+            mUserIds = Objects.requireNonNull(userIds);
+            mLocale = Objects.requireNonNull(locale);
+        }
+
+        int keyphraseId() {
+            return mKeyphraseId;
+        }
+
+        List<Integer> userIds() {
+            return mUserIds;
+        }
+
+        String locale() {
+            return mLocale;
+        }
+
+        @Override
+        public String toString() {
+            StringJoiner sj = new StringJoiner(", ", "{", "}");
+            sj.add("keyphraseId: " + mKeyphraseId);
+            sj.add("userIds: " + mUserIds.toString());
+            sj.add("locale: " + mLocale.toString());
+            return "EnrollmentKey: " + sj.toString();
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int res = 1;
+            res = prime * res + mKeyphraseId;
+            res = prime * res + mUserIds.hashCode();
+            res = prime * res + mLocale.hashCode();
+            return res;
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (this == other) return true;
+            if (other == null) return false;
+            if (!(other instanceof EnrollmentKey)) return false;
+            EnrollmentKey that = (EnrollmentKey) other;
+            if (mKeyphraseId != that.mKeyphraseId) return false;
+            if (!mUserIds.equals(that.mUserIds)) return false;
+            if (!mLocale.equals(that.mLocale)) return false;
+            return true;
+        }
+
+    }
+
+    private final Map<EnrollmentKey, KeyphraseSoundModel> mModelMap = new HashMap<>();
+
+    @Override
+    public boolean updateKeyphraseSoundModel(KeyphraseSoundModel soundModel) {
+        final Keyphrase keyphrase = soundModel.getKeyphrases()[0];
+        mModelMap.put(new EnrollmentKey(keyphrase.getId(),
+                        Arrays.stream(keyphrase.getUsers()).boxed().toList(),
+                        keyphrase.getLocale().toLanguageTag()),
+                    soundModel);
+        return true;
+    }
+
+    @Override
+    public boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale) {
+        return mModelMap.keySet().removeIf(key -> (key.keyphraseId() == keyphraseId)
+                && key.locale().equals(bcp47Locale)
+                && key.userIds().contains(userHandle));
+    }
+
+    @Override
+    public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, int userHandle,
+            String bcp47Locale) {
+        return mModelMap.entrySet()
+                .stream()
+                .filter((entry) -> (entry.getKey().keyphraseId() == keyphraseId)
+                        && entry.getKey().locale().equals(bcp47Locale)
+                        && entry.getKey().userIds().contains(userHandle))
+                .findFirst()
+                .map((entry) -> entry.getValue())
+                .orElse(null);
+    }
+
+    @Override
+    public KeyphraseSoundModel getKeyphraseSoundModel(String keyphrase, int userHandle,
+            String bcp47Locale) {
+        return mModelMap.entrySet()
+                .stream()
+                .filter((entry) -> (entry.getValue().getKeyphrases()[0].getText().equals(keyphrase)
+                        && entry.getKey().locale().equals(bcp47Locale)
+                        && entry.getKey().userIds().contains(userHandle)))
+                .findFirst()
+                .map((entry) -> entry.getValue())
+                .orElse(null);
+    }
+
+
+    /**
+     * Dumps contents of database for dumpsys
+     */
+    public void dump(PrintWriter pw) {
+        pw.println("Using test enrollment database, with enrolled models:");
+        pw.println(mModelMap);
+    }
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index e1da2ca..1d7b966 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -99,11 +99,11 @@
 import com.android.internal.util.DumpUtils;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
+import com.android.server.SoundTriggerInternal;
 import com.android.server.SystemService;
 import com.android.server.UiThread;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.permission.LegacyPermissionManagerInternal;
-import com.android.server.soundtrigger.SoundTriggerInternal;
 import com.android.server.utils.Slogf;
 import com.android.server.utils.TimingsTraceAndSlog;
 import com.android.server.wm.ActivityTaskManagerInternal;
@@ -125,7 +125,9 @@
 
     final Context mContext;
     final ContentResolver mResolver;
-    final DatabaseHelper mDbHelper;
+    // Can be overridden for testing purposes
+    private IEnrolledModelDb mDbHelper;
+    private final IEnrolledModelDb mRealDbHelper;
     final ActivityManagerInternal mAmInternal;
     final ActivityTaskManagerInternal mAtmInternal;
     final UserManagerInternal mUserManagerInternal;
@@ -143,7 +145,7 @@
         mResolver = context.getContentResolver();
         mUserManagerInternal = Objects.requireNonNull(
                 LocalServices.getService(UserManagerInternal.class));
-        mDbHelper = new DatabaseHelper(context);
+        mDbHelper = mRealDbHelper = new DatabaseHelper(context);
         mServiceStub = new VoiceInteractionManagerServiceStub();
         mAmInternal = Objects.requireNonNull(
                 LocalServices.getService(ActivityManagerInternal.class));
@@ -1605,6 +1607,42 @@
             }
         }
 
+        @Override
+        @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_VOICE_KEYPHRASES)
+        public void setModelDatabaseForTestEnabled(boolean enabled, IBinder token) {
+            super.setModelDatabaseForTestEnabled_enforcePermission();
+            enforceCallerAllowedToEnrollVoiceModel();
+            synchronized (this) {
+                if (enabled) {
+                    // Replace the dbhelper with a new test db
+                    final var db = new TestModelEnrollmentDatabase();
+                    try {
+                        // Listen to our caller death, and make sure we revert to the real
+                        // db if they left the model in a test state.
+                        token.linkToDeath(() -> {
+                            synchronized (this) {
+                                if (mDbHelper == db) {
+                                    mDbHelper = mRealDbHelper;
+                                    mImpl.notifySoundModelsChangedLocked();
+                                }
+                            }
+                        }, 0);
+                    } catch (RemoteException e) {
+                        // If the caller is already dead, nothing to do.
+                        return;
+                    }
+                    mDbHelper = db;
+                    mImpl.notifySoundModelsChangedLocked();
+                } else {
+                    // Nothing to do if the db is already set to the real impl.
+                    if (mDbHelper != mRealDbHelper) {
+                        mDbHelper = mRealDbHelper;
+                        mImpl.notifySoundModelsChangedLocked();
+                    }
+                }
+            }
+        }
+
         //----------------- SoundTrigger APIs --------------------------------//
         @Override
         public boolean isEnrolledForKeyphrase(int keyphraseId, String bcp47Locale) {
@@ -1712,28 +1750,27 @@
                 final long caller = Binder.clearCallingIdentity();
                 try {
                     KeyphraseSoundModel soundModel =
-                            mDbHelper.getKeyphraseSoundModel(keyphraseId, callingUserId, bcp47Locale);
+                            mDbHelper.getKeyphraseSoundModel(keyphraseId,
+                                    callingUserId, bcp47Locale);
                     if (soundModel == null
                             || soundModel.getUuid() == null
                             || soundModel.getKeyphrases() == null) {
                         Slog.w(TAG, "No matching sound model found in startRecognition");
                         return SoundTriggerInternal.STATUS_ERROR;
-                    } else {
-                        // Regardless of the status of the start recognition, we need to make sure
-                        // that we unload this model if needed later.
-                        synchronized (VoiceInteractionManagerServiceStub.this) {
-                            mLoadedKeyphraseIds.put(keyphraseId, this);
-                            if (mSessionExternalCallback == null
-                                    || mSessionInternalCallback == null
-                                    || callback.asBinder() != mSessionExternalCallback.asBinder()) {
-                                mSessionInternalCallback = createSoundTriggerCallbackLocked(
-                                        callback);
-                                mSessionExternalCallback = callback;
-                            }
-                        }
-                        return mSession.startRecognition(keyphraseId, soundModel,
-                                mSessionInternalCallback, recognitionConfig, runInBatterySaverMode);
                     }
+                    // Regardless of the status of the start recognition, we need to make sure
+                    // that we unload this model if needed later.
+                    synchronized (VoiceInteractionManagerServiceStub.this) {
+                        mLoadedKeyphraseIds.put(keyphraseId, this);
+                        if (mSessionExternalCallback == null
+                                || mSessionInternalCallback == null
+                                || callback.asBinder() != mSessionExternalCallback.asBinder()) {
+                            mSessionInternalCallback = createSoundTriggerCallbackLocked(callback);
+                            mSessionExternalCallback = callback;
+                        }
+                    }
+                    return mSession.startRecognition(keyphraseId, soundModel,
+                            mSessionInternalCallback, recognitionConfig, runInBatterySaverMode);
                 } finally {
                     Binder.restoreCallingIdentity(caller);
                 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 62be2a55..0ad86c1 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -71,7 +71,6 @@
 import android.util.Slog;
 import android.view.IWindowManager;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IHotwordRecognitionStatusCallback;
 import com.android.internal.app.IVisualQueryDetectionAttentionListener;
 import com.android.internal.app.IVoiceActionCheckCallback;
@@ -248,7 +247,6 @@
                 Context.RECEIVER_EXPORTED);
     }
 
-    @GuardedBy("this")
     public void grantImplicitAccessLocked(int grantRecipientUid, @Nullable Intent intent) {
         final int grantRecipientAppId = UserHandle.getAppId(grantRecipientUid);
         final int grantRecipientUserId = UserHandle.getUserId(grantRecipientUid);
@@ -258,7 +256,6 @@
                 /* direct= */ true);
     }
 
-    @GuardedBy("this")
     public boolean showSessionLocked(@Nullable Bundle args, int flags,
             @Nullable String attributionTag,
             @Nullable IVoiceInteractionSessionShowCallback showCallback,
@@ -331,7 +328,6 @@
         }
     }
 
-    @GuardedBy("this")
     public boolean hideSessionLocked() {
         if (mActiveSession != null) {
             return mActiveSession.hideLocked();
@@ -339,7 +335,6 @@
         return false;
     }
 
-    @GuardedBy("this")
     public boolean deliverNewSessionLocked(IBinder token,
             IVoiceInteractionSession session, IVoiceInteractor interactor) {
         if (mActiveSession == null || token != mActiveSession.mToken) {
@@ -350,7 +345,6 @@
         return true;
     }
 
-    @GuardedBy("this")
     public int startVoiceActivityLocked(@Nullable String callingFeatureId, int callingPid,
             int callingUid, IBinder token, Intent intent, String resolvedType) {
         try {
@@ -373,7 +367,6 @@
         }
     }
 
-    @GuardedBy("this")
     public int startAssistantActivityLocked(@Nullable String callingFeatureId, int callingPid,
             int callingUid, IBinder token, Intent intent, String resolvedType,
             @NonNull Bundle bundle) {
@@ -397,7 +390,6 @@
         }
     }
 
-    @GuardedBy("this")
     public void requestDirectActionsLocked(@NonNull IBinder token, int taskId,
             @NonNull IBinder assistToken,  @Nullable RemoteCallback cancellationCallback,
             @NonNull RemoteCallback callback) {
@@ -453,7 +445,6 @@
         }
     }
 
-    @GuardedBy("this")
     void performDirectActionLocked(@NonNull IBinder token, @NonNull String actionId,
             @Nullable Bundle arguments, int taskId, IBinder assistToken,
             @Nullable RemoteCallback cancellationCallback,
@@ -480,7 +471,6 @@
         }
     }
 
-    @GuardedBy("this")
     public void setKeepAwakeLocked(IBinder token, boolean keepAwake) {
         try {
             if (mActiveSession == null || token != mActiveSession.mToken) {
@@ -493,7 +483,6 @@
         }
     }
 
-    @GuardedBy("this")
     public void closeSystemDialogsLocked(IBinder token) {
         try {
             if (mActiveSession == null || token != mActiveSession.mToken) {
@@ -506,7 +495,6 @@
         }
     }
 
-    @GuardedBy("this")
     public void finishLocked(IBinder token, boolean finishTask) {
         if (mActiveSession == null || (!finishTask && token != mActiveSession.mToken)) {
             Slog.w(TAG, "finish does not match active session");
@@ -516,7 +504,6 @@
         mActiveSession = null;
     }
 
-    @GuardedBy("this")
     public void setDisabledShowContextLocked(int callingUid, int flags) {
         int activeUid = mInfo.getServiceInfo().applicationInfo.uid;
         if (callingUid != activeUid) {
@@ -526,7 +513,6 @@
         mDisabledShowContext = flags;
     }
 
-    @GuardedBy("this")
     public int getDisabledShowContextLocked(int callingUid) {
         int activeUid = mInfo.getServiceInfo().applicationInfo.uid;
         if (callingUid != activeUid) {
@@ -536,7 +522,6 @@
         return mDisabledShowContext;
     }
 
-    @GuardedBy("this")
     public int getUserDisabledShowContextLocked(int callingUid) {
         int activeUid = mInfo.getServiceInfo().applicationInfo.uid;
         if (callingUid != activeUid) {
@@ -550,7 +535,6 @@
         return mInfo.getSupportsLocalInteraction();
     }
 
-    @GuardedBy("this")
     public void startListeningVisibleActivityChangedLocked(@NonNull IBinder token) {
         if (DEBUG) {
             Slog.d(TAG, "startListeningVisibleActivityChangedLocked: token=" + token);
@@ -563,7 +547,6 @@
         mActiveSession.startListeningVisibleActivityChangedLocked();
     }
 
-    @GuardedBy("this")
     public void stopListeningVisibleActivityChangedLocked(@NonNull IBinder token) {
         if (DEBUG) {
             Slog.d(TAG, "stopListeningVisibleActivityChangedLocked: token=" + token);
@@ -576,7 +559,6 @@
         mActiveSession.stopListeningVisibleActivityChangedLocked();
     }
 
-    @GuardedBy("this")
     public void notifyActivityDestroyedLocked(@NonNull IBinder activityToken) {
         if (DEBUG) {
             Slog.d(TAG, "notifyActivityDestroyedLocked activityToken=" + activityToken);
@@ -591,7 +573,6 @@
         mActiveSession.notifyActivityDestroyedLocked(activityToken);
     }
 
-    @GuardedBy("this")
     public void notifyActivityEventChangedLocked(@NonNull IBinder activityToken, int type) {
         if (DEBUG) {
             Slog.d(TAG, "notifyActivityEventChangedLocked type=" + type);
@@ -606,7 +587,6 @@
         mActiveSession.notifyActivityEventChangedLocked(activityToken, type);
     }
 
-    @GuardedBy("this")
     public void updateStateLocked(
             @Nullable PersistableBundle options,
             @Nullable SharedMemory sharedMemory,
@@ -627,7 +607,6 @@
         }
     }
 
-    @GuardedBy("this")
     private void verifyDetectorForHotwordDetectionLocked(
             @Nullable SharedMemory sharedMemory,
             IHotwordRecognitionStatusCallback callback,
@@ -685,7 +664,6 @@
                 voiceInteractionServiceUid);
     }
 
-    @GuardedBy("this")
     private void verifyDetectorForVisualQueryDetectionLocked(@Nullable SharedMemory sharedMemory) {
         Slog.v(TAG, "verifyDetectorForVisualQueryDetectionLocked");
 
@@ -724,7 +702,6 @@
         }
     }
 
-    @GuardedBy("this")
     public void initAndVerifyDetectorLocked(
             @NonNull Identity voiceInteractorIdentity,
             @Nullable PersistableBundle options,
@@ -769,7 +746,6 @@
                 detectorType);
     }
 
-    @GuardedBy("this")
     public void destroyDetectorLocked(IBinder token) {
         Slog.v(TAG, "destroyDetectorLocked");
 
@@ -788,7 +764,6 @@
         }
     }
 
-    @GuardedBy("this")
     public void shutdownHotwordDetectionServiceLocked() {
         if (DEBUG) {
             Slog.d(TAG, "shutdownHotwordDetectionServiceLocked");
@@ -801,7 +776,6 @@
         mHotwordDetectionConnection = null;
     }
 
-    @GuardedBy("this")
     public void setVisualQueryDetectionAttentionListenerLocked(
             @Nullable IVisualQueryDetectionAttentionListener listener) {
         if (mHotwordDetectionConnection == null) {
@@ -810,7 +784,6 @@
         mHotwordDetectionConnection.setVisualQueryDetectionAttentionListenerLocked(listener);
     }
 
-    @GuardedBy("this")
     public void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
         if (DEBUG) {
             Slog.d(TAG, "startPerceivingLocked");
@@ -824,7 +797,6 @@
         mHotwordDetectionConnection.startPerceivingLocked(callback);
     }
 
-    @GuardedBy("this")
     public void stopPerceivingLocked() {
         if (DEBUG) {
             Slog.d(TAG, "stopPerceivingLocked");
@@ -838,7 +810,6 @@
         mHotwordDetectionConnection.stopPerceivingLocked();
     }
 
-    @GuardedBy("this")
     public void startListeningFromMicLocked(
             AudioFormat audioFormat,
             IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
@@ -854,7 +825,6 @@
         mHotwordDetectionConnection.startListeningFromMicLocked(audioFormat, callback);
     }
 
-    @GuardedBy("this")
     public void startListeningFromExternalSourceLocked(
             ParcelFileDescriptor audioStream,
             AudioFormat audioFormat,
@@ -879,7 +849,6 @@
                 options, token, callback);
     }
 
-    @GuardedBy("this")
     public void stopListeningFromMicLocked() {
         if (DEBUG) {
             Slog.d(TAG, "stopListeningFromMicLocked");
@@ -893,7 +862,6 @@
         mHotwordDetectionConnection.stopListeningFromMicLocked();
     }
 
-    @GuardedBy("this")
     public void triggerHardwareRecognitionEventForTestLocked(
             SoundTrigger.KeyphraseRecognitionEvent event,
             IHotwordRecognitionStatusCallback callback) {
@@ -908,7 +876,6 @@
         mHotwordDetectionConnection.triggerHardwareRecognitionEventForTestLocked(event, callback);
     }
 
-    @GuardedBy("this")
     public IRecognitionStatusCallback createSoundTriggerCallbackLocked(
             IHotwordRecognitionStatusCallback callback) {
         if (DEBUG) {
@@ -933,12 +900,11 @@
         return null;
     }
 
-    @GuardedBy("this")
     boolean isIsolatedProcessLocked(@NonNull ServiceInfo serviceInfo) {
         return (serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0
                 && (serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) == 0;
     }
-    @GuardedBy("this")
+
     boolean verifyProcessSharingLocked() {
         // only check this if both VQDS and HDS are declared in the app
         ServiceInfo hotwordInfo = getServiceInfoLocked(mHotwordDetectionComponentName, mUser);
@@ -960,7 +926,6 @@
         mHotwordDetectionConnection.forceRestart();
     }
 
-    @GuardedBy("this")
     void setDebugHotwordLoggingLocked(boolean logging) {
         if (mHotwordDetectionConnection == null) {
             Slog.w(TAG, "Failed to set temporary debug logging: no hotword detection active");
@@ -969,7 +934,6 @@
         mHotwordDetectionConnection.setDebugHotwordLoggingLocked(logging);
     }
 
-    @GuardedBy("this")
     void resetHotwordDetectionConnectionLocked() {
         if (DEBUG) {
             Slog.d(TAG, "resetHotwordDetectionConnectionLocked");
@@ -984,7 +948,6 @@
         mHotwordDetectionConnection = null;
     }
 
-    @GuardedBy("this")
     public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!mValid) {
             pw.print("  NOT VALID: ");
@@ -1023,7 +986,6 @@
         }
     }
 
-    @GuardedBy("this")
     void startLocked() {
         Intent intent = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
         intent.setComponent(mComponent);
@@ -1048,7 +1010,6 @@
         }
     }
 
-    @GuardedBy("this")
     void shutdownLocked() {
         // If there is an active session, cancel it to allow it to clean up its window and other
         // state.
@@ -1076,7 +1037,6 @@
         }
     }
 
-    @GuardedBy("this")
     void notifySoundModelsChangedLocked() {
         if (mService == null) {
             Slog.w(TAG, "Not bound to voice interaction service " + mComponent);
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index e39af5a..9dd2a61 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -2682,71 +2682,76 @@
     }
 
     /**
-     * Reports a new call with the specified {@link CallAttributes} to the telecom service. This
-     * method can be used to report both incoming and outgoing calls.  By reporting the call, the
-     * system is aware of the call and can provide updates on services (ex. Another device wants to
-     * disconnect the call) or events (ex. a new Bluetooth route became available).
-     *
+     * Add a call to the Android system service Telecom. This allows the system to start tracking an
+     * incoming or outgoing call with the specified {@link CallAttributes}. Once the call is ready
+     * to be disconnected, use the {@link CallControl#disconnect(DisconnectCause, Executor,
+     * OutcomeReceiver)} which is provided by the {@code pendingControl#onResult(CallControl)}.
      * <p>
-     * The difference between this API call and {@link TelecomManager#placeCall(Uri, Bundle)} or
-     * {@link TelecomManager#addNewIncomingCall(PhoneAccountHandle, Bundle)} is that this API
-     * will asynchronously provide an update on whether the new call was added successfully via
-     * an {@link OutcomeReceiver}.  Additionally, callbacks will run on the executor thread that was
-     * passed in.
-     *
      * <p>
-     * Note: Only packages that register with
+     * <p>
+     * <b>Call Lifecycle</b>: Your app is given foreground execution priority as long as you have a
+     * valid call and are posting a {@link android.app.Notification.CallStyle} notification.
+     * When your application is given foreground execution priority, your app is treated as a
+     * foreground service. Foreground execution priority will prevent the
+     * {@link android.app.ActivityManager} from killing your application when it is placed the
+     * background. Foreground execution priority is removed from your app when all of your app's
+     * calls terminate or your app no longer posts a valid notification.
+     * <p>
+     * <p>
+     * <p>
+     * <b>Note</b>: Only packages that register with
      * {@link PhoneAccount#CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS}
      * can utilize this API. {@link PhoneAccount}s that set the capabilities
      * {@link PhoneAccount#CAPABILITY_SIM_SUBSCRIPTION},
      * {@link PhoneAccount#CAPABILITY_CALL_PROVIDER},
      * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}
      * are not supported and will cause an exception to be thrown.
-     *
      * <p>
-     * Usage example:
+     * <p>
+     * <p>
+     * <b>Usage example:</b>
      * <pre>
-     *
-     *  // An app should first define their own construct of a Call that overrides all the
-     *  // {@link CallControlCallback}s and {@link CallEventCallback}s
-     *  private class MyVoipCall {
-     *   public String callId = "";
-     *
-     *   public CallControlCallEventCallback handshakes = new
-     *                       CallControlCallEventCallback() {
-     *                         // override/ implement all {@link CallControlCallback}s
+     *  // Its up to your app on how you want to wrap the objects. One such implementation can be:
+     *  class MyVoipCall {
+     *    ...
+     *      public CallControlCallEventCallback handshakes = new  CallControlCallback() {
+     *                         ...
      *                        }
-     *   public CallEventCallback events = new
-     *                       CallEventCallback() {
-     *                         // override/ implement all {@link CallEventCallback}s
+     *
+     *      public CallEventCallback events = new CallEventCallback() {
+     *                         ...
      *                        }
-     *   public MyVoipCall(String id){
-     *       callId = id;
+     *
+     *      public MyVoipCall(String id){
+     *          ...
+     *      }
      *  }
      *
-     * PhoneAccountHandle handle = new PhoneAccountHandle(
-     *                          new ComponentName("com.example.voip.app",
-     *                                            "com.example.voip.app.NewCallActivity"), "123");
-     *
-     * CallAttributes callAttributes = new CallAttributes.Builder(handle,
-     *                                             CallAttributes.DIRECTION_OUTGOING,
-     *                                            "John Smith", Uri.fromParts("tel", "123", null))
-     *                                            .build();
-     *
      * MyVoipCall myFirstOutgoingCall = new MyVoipCall("1");
      *
-     * telecomManager.addCall(callAttributes, Runnable::run, new OutcomeReceiver() {
+     * telecomManager.addCall(callAttributes,
+     *                        Runnable::run,
+     *                        new OutcomeReceiver() {
      *                              public void onResult(CallControl callControl) {
-     *                                 // The call has been added successfully
+     *                                 // The call has been added successfully. For demonstration
+     *                                 // purposes, the call is disconnected immediately ...
+     *                                 callControl.disconnect(
+     *                                                 new DisconnectCause(DisconnectCause.LOCAL) )
      *                              }
-     *                           }, myFirstOutgoingCall.handshakes, myFirstOutgoingCall.events);
+     *                           },
+     *                           myFirstOutgoingCall.handshakes,
+     *                           myFirstOutgoingCall.events);
      * </pre>
      *
-     * @param callAttributes    attributes of the new call (incoming or outgoing, address, etc. )
-     * @param executor          thread to run background CallEventCallback updates on
-     * @param pendingControl    OutcomeReceiver that receives the result of addCall transaction
-     * @param handshakes        object that overrides {@link CallControlCallback}s
-     * @param events            object that overrides {@link CallEventCallback}s
+     * @param callAttributes attributes of the new call (incoming or outgoing, address, etc.)
+     * @param executor       execution context to run {@link CallControlCallback} updates on
+     * @param pendingControl Receives the result of addCall transaction. Upon success, a
+     *                       CallControl object is provided which can be used to do things like
+     *                       disconnect the call that was added.
+     * @param handshakes     callback that receives <b>actionable</b> updates that originate from
+     *                       Telecom.
+     * @param events         callback that receives <b>non</b>-actionable updates that originate
+     *                       from Telecom.
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_OWN_CALLS)
     @SuppressLint("SamShouldBeLast")
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 78c6196..559faf9 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -3142,10 +3142,10 @@
      *
      * Only supported for embedded subscriptions (if {@link SubscriptionInfo#isEmbedded} returns
      * true). To check for permissions for non-embedded subscription as well,
+     * see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}.
      *
      * @param info The subscription to check.
      * @return whether the app is authorized to manage this subscription per its metadata.
-     *
      * @see android.telephony.TelephonyManager#hasCarrierPrivileges
      */
     public boolean canManageSubscription(SubscriptionInfo info) {
@@ -3159,12 +3159,12 @@
      *
      * Only supported for embedded subscriptions (if {@link SubscriptionInfo#isEmbedded} returns
      * true). To check for permissions for non-embedded subscription as well,
+     * see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}.
      *
      * @param info The subscription to check.
      * @param packageName Package name of the app to check.
      *
      * @return whether the app is authorized to manage this subscription per its access rules.
-     *
      * @see android.telephony.TelephonyManager#hasCarrierPrivileges
      * @hide
      */
diff --git a/core/java/com/android/internal/expresslog/Utils.java b/telephony/java/android/telephony/satellite/AntennaDirection.aidl
similarity index 69%
copy from core/java/com/android/internal/expresslog/Utils.java
copy to telephony/java/android/telephony/satellite/AntennaDirection.aidl
index d82192f..c838f6f 100644
--- a/core/java/com/android/internal/expresslog/Utils.java
+++ b/telephony/java/android/telephony/satellite/AntennaDirection.aidl
@@ -1,11 +1,11 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright 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
+ *     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,
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.internal.expresslog;
+package android.telephony.satellite;
 
-final class Utils {
-    static native long hashString(String stringToHash);
-}
+parcelable AntennaDirection;
diff --git a/telephony/java/android/telephony/satellite/AntennaDirection.java b/telephony/java/android/telephony/satellite/AntennaDirection.java
new file mode 100644
index 0000000..02b0bc7
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/AntennaDirection.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Antenna direction is provided as X/Y/Z values corresponding to the direction of the antenna
+ * main lobe as a unit vector in CTIA coordinate system (as specified in Appendix A of Wireless
+ * device CTIA OTAn test plan). CTIA coordinate system is defined relative to device’s screen
+ * when the device is held in default portrait mode with screen facing the user:
+ *
+ * Z axis is vertical along the plane of the device with positive Z pointing up and negative z
+ * pointing towards bottom of the device
+ * Y axis is horizontal along the plane of the device with positive Y pointing towards right of
+ * the phone screen and negative Y pointing towards left
+ * X axis is orthogonal to the Y-Z plane (phone screen), pointing away from the phone screen for
+ * positive X and pointing away from back of the phone for negative X.
+ * @hide
+ */
+public final class AntennaDirection implements Parcelable {
+    /** Antenna x axis direction. */
+    private float mX;
+
+    /** Antenna y axis direction. */
+    private float mY;
+
+    /** Antenna z axis direction. */
+    private float mZ;
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public AntennaDirection(float x, float y, float z) {
+        mX = x;
+        mY = y;
+        mZ = z;
+    }
+
+    private AntennaDirection(Parcel in) {
+        readFromParcel(in);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeFloat(mX);
+        out.writeFloat(mY);
+        out.writeFloat(mZ);
+    }
+
+    @NonNull
+    public static final Creator<AntennaDirection> CREATOR =
+            new Creator<>() {
+                @Override
+                public AntennaDirection createFromParcel(Parcel in) {
+                    return new AntennaDirection(in);
+                }
+
+                @Override
+                public AntennaDirection[] newArray(int size) {
+                    return new AntennaDirection[size];
+                }
+            };
+
+    @Override
+    @NonNull public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("X:");
+        sb.append(mX);
+        sb.append(",");
+
+        sb.append("Y:");
+        sb.append(mY);
+        sb.append(",");
+
+        sb.append("Z:");
+        sb.append(mZ);
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        AntennaDirection that = (AntennaDirection) o;
+        return mX == that.mX
+                && mY == that.mY
+                && mZ == that.mZ;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mX, mY, mZ);
+    }
+
+    public float getX() {
+        return mX;
+    }
+
+    public float getY() {
+        return mY;
+    }
+
+    public float getZ() {
+        return mZ;
+    }
+
+    private void readFromParcel(Parcel in) {
+        mX = in.readFloat();
+        mY = in.readFloat();
+        mZ = in.readFloat();
+    }
+}
diff --git a/core/java/com/android/internal/expresslog/Utils.java b/telephony/java/android/telephony/satellite/AntennaPosition.aidl
similarity index 69%
copy from core/java/com/android/internal/expresslog/Utils.java
copy to telephony/java/android/telephony/satellite/AntennaPosition.aidl
index d82192f..0052562 100644
--- a/core/java/com/android/internal/expresslog/Utils.java
+++ b/telephony/java/android/telephony/satellite/AntennaPosition.aidl
@@ -1,11 +1,11 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright 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
+ *     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,
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.internal.expresslog;
+package android.telephony.satellite;
 
-final class Utils {
-    static native long hashString(String stringToHash);
-}
+parcelable AntennaPosition;
diff --git a/telephony/java/android/telephony/satellite/AntennaPosition.java b/telephony/java/android/telephony/satellite/AntennaPosition.java
new file mode 100644
index 0000000..eefc8b0
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/AntennaPosition.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Antenna Position received from satellite modem which gives information about antenna
+ * direction to be used with satellite communication and suggested device hold positions.
+ * @hide
+ */
+public final class AntennaPosition implements Parcelable {
+    /** Antenna direction used for satellite communication. */
+    @NonNull AntennaDirection mAntennaDirection;
+
+    /** Enum corresponding to device hold position to be used by the end user. */
+    @SatelliteManager.DeviceHoldPosition int mSuggestedHoldPosition;
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public AntennaPosition(@NonNull AntennaDirection antennaDirection, int suggestedHoldPosition) {
+        mAntennaDirection = antennaDirection;
+        mSuggestedHoldPosition = suggestedHoldPosition;
+    }
+
+    private AntennaPosition(Parcel in) {
+        readFromParcel(in);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeParcelable(mAntennaDirection, flags);
+        out.writeInt(mSuggestedHoldPosition);
+    }
+
+    @NonNull
+    public static final Creator<AntennaPosition> CREATOR =
+            new Creator<>() {
+                @Override
+                public AntennaPosition createFromParcel(Parcel in) {
+                    return new AntennaPosition(in);
+                }
+
+                @Override
+                public AntennaPosition[] newArray(int size) {
+                    return new AntennaPosition[size];
+                }
+            };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        AntennaPosition that = (AntennaPosition) o;
+        return Objects.equals(mAntennaDirection, that.mAntennaDirection)
+                && mSuggestedHoldPosition == that.mSuggestedHoldPosition;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mAntennaDirection, mSuggestedHoldPosition);
+    }
+
+    @Override
+    @NonNull public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("antennaDirection:");
+        sb.append(mAntennaDirection);
+        sb.append(",");
+
+        sb.append("suggestedHoldPosition:");
+        sb.append(mSuggestedHoldPosition);
+        return sb.toString();
+    }
+
+    @NonNull
+    public AntennaDirection getAntennaDirection() {
+        return mAntennaDirection;
+    }
+
+    @SatelliteManager.DeviceHoldPosition
+    public int getSuggestedHoldPosition() {
+        return mSuggestedHoldPosition;
+    }
+
+    private void readFromParcel(Parcel in) {
+        mAntennaDirection = in.readParcelable(AntennaDirection.class.getClassLoader(),
+                AntennaDirection.class);
+        mSuggestedHoldPosition = in.readInt();
+    }
+}
diff --git a/telephony/java/android/telephony/satellite/PointingInfo.java b/telephony/java/android/telephony/satellite/PointingInfo.java
index e8a8576..a559b32 100644
--- a/telephony/java/android/telephony/satellite/PointingInfo.java
+++ b/telephony/java/android/telephony/satellite/PointingInfo.java
@@ -17,7 +17,6 @@
 package android.telephony.satellite;
 
 import android.annotation.NonNull;
-import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -34,7 +33,7 @@
     /**
      * @hide
      */
-    @UnsupportedAppUsage
+
     public PointingInfo(float satelliteAzimuthDegrees, float satelliteElevationDegrees) {
         mSatelliteAzimuthDegrees = satelliteAzimuthDegrees;
         mSatelliteElevationDegrees = satelliteElevationDegrees;
diff --git a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
index 87c8db3..6856cc0 100644
--- a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
+++ b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
@@ -17,11 +17,13 @@
 package android.telephony.satellite;
 
 import android.annotation.NonNull;
-import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -44,15 +46,24 @@
     private int mMaxBytesPerOutgoingDatagram;
 
     /**
+     * Antenna Position received from satellite modem which gives information about antenna
+     * direction to be used with satellite communication and suggested device hold positions.
+     * Map key: {@link SatelliteManager.DeviceHoldPosition} value: AntennaPosition
+     */
+    @NonNull
+    private Map<Integer, AntennaPosition> mAntennaPositionMap;
+
+    /**
      * @hide
      */
-    @UnsupportedAppUsage
     public SatelliteCapabilities(Set<Integer> supportedRadioTechnologies,
-            boolean isPointingRequired, int maxBytesPerOutgoingDatagram) {
+            boolean isPointingRequired, int maxBytesPerOutgoingDatagram,
+            @NonNull Map<Integer, AntennaPosition> antennaPositionMap) {
         mSupportedRadioTechnologies = supportedRadioTechnologies == null
                 ? new HashSet<>() : supportedRadioTechnologies;
         mIsPointingRequired = isPointingRequired;
         mMaxBytesPerOutgoingDatagram = maxBytesPerOutgoingDatagram;
+        mAntennaPositionMap = antennaPositionMap;
     }
 
     private SatelliteCapabilities(Parcel in) {
@@ -77,6 +88,17 @@
 
         out.writeBoolean(mIsPointingRequired);
         out.writeInt(mMaxBytesPerOutgoingDatagram);
+
+        if (mAntennaPositionMap != null && !mAntennaPositionMap.isEmpty()) {
+            int size = mAntennaPositionMap.size();
+            out.writeInt(size);
+            for (Map.Entry<Integer, AntennaPosition> entry : mAntennaPositionMap.entrySet()) {
+                out.writeInt(entry.getKey());
+                out.writeParcelable(entry.getValue(), flags);
+            }
+        } else {
+            out.writeInt(0);
+        }
     }
 
     @NonNull public static final Creator<SatelliteCapabilities> CREATOR = new Creator<>() {
@@ -109,11 +131,32 @@
         sb.append(mIsPointingRequired);
         sb.append(",");
 
-        sb.append("maxBytesPerOutgoingDatagram");
+        sb.append("maxBytesPerOutgoingDatagram:");
         sb.append(mMaxBytesPerOutgoingDatagram);
+        sb.append(",");
+
+        sb.append("antennaPositionMap:");
+        sb.append(mAntennaPositionMap);
         return sb.toString();
     }
 
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        SatelliteCapabilities that = (SatelliteCapabilities) o;
+        return Objects.equals(mSupportedRadioTechnologies, that.mSupportedRadioTechnologies)
+                && mIsPointingRequired == that.mIsPointingRequired
+                && mMaxBytesPerOutgoingDatagram == that.mMaxBytesPerOutgoingDatagram
+                && Objects.equals(mAntennaPositionMap, that.mAntennaPositionMap);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mSupportedRadioTechnologies, mIsPointingRequired,
+                mMaxBytesPerOutgoingDatagram, mAntennaPositionMap);
+    }
+
     /**
      * @return The list of technologies supported by the satellite modem.
      */
@@ -141,6 +184,16 @@
         return mMaxBytesPerOutgoingDatagram;
     }
 
+    /**
+     * Antenna Position received from satellite modem which gives information about antenna
+     * direction to be used with satellite communication and suggested device hold positions.
+     * @return Map key: {@link SatelliteManager.DeviceHoldPosition} value: AntennaPosition
+     */
+    @NonNull
+    public Map<Integer, AntennaPosition> getAntennaPositionMap() {
+        return mAntennaPositionMap;
+    }
+
     private void readFromParcel(Parcel in) {
         mSupportedRadioTechnologies = new HashSet<>();
         int numSupportedRadioTechnologies = in.readInt();
@@ -152,5 +205,14 @@
 
         mIsPointingRequired = in.readBoolean();
         mMaxBytesPerOutgoingDatagram = in.readInt();
+
+        mAntennaPositionMap = new HashMap<>();
+        int antennaPositionMapSize = in.readInt();
+        for (int i = 0; i < antennaPositionMapSize; i++) {
+            int key = in.readInt();
+            AntennaPosition antennaPosition = in.readParcelable(
+                    AntennaPosition.class.getClassLoader(), AntennaPosition.class);
+            mAntennaPositionMap.put(key, antennaPosition);
+        }
     }
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagram.java b/telephony/java/android/telephony/satellite/SatelliteDatagram.java
index 44f56f3..d3cb8a0 100644
--- a/telephony/java/android/telephony/satellite/SatelliteDatagram.java
+++ b/telephony/java/android/telephony/satellite/SatelliteDatagram.java
@@ -17,7 +17,6 @@
 package android.telephony.satellite;
 
 import android.annotation.NonNull;
-import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -33,7 +32,6 @@
     /**
      * @hide
      */
-    @UnsupportedAppUsage
     public SatelliteDatagram(@NonNull byte[] data) {
         mData = data;
     }
diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
index d0409bf..b2dec71 100644
--- a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
@@ -17,7 +17,6 @@
 package android.telephony.satellite;
 
 import android.annotation.NonNull;
-import android.compat.annotation.UnsupportedAppUsage;
 
 import java.util.function.Consumer;
 
@@ -37,7 +36,6 @@
      *                 that they received the datagram. If the callback is not received within
      *                 five minutes, Telephony will resend the datagram.
      */
-    @UnsupportedAppUsage
     void onSatelliteDatagramReceived(long datagramId, @NonNull SatelliteDatagram datagram,
             int pendingCount, @NonNull Consumer<Void> callback);
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 20f9bc8b..c5830b8 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -23,7 +23,6 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresFeature;
 import android.annotation.RequiresPermission;
-import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.Binder;
@@ -37,8 +36,8 @@
 import android.telephony.TelephonyFrameworkInitializer;
 
 import com.android.internal.telephony.IIntegerConsumer;
-import com.android.internal.telephony.IVoidConsumer;
 import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.IVoidConsumer;
 import com.android.telephony.Rlog;
 
 import java.lang.annotation.Retention;
@@ -83,7 +82,7 @@
      * @param context The context the SatelliteManager belongs to.
      * @hide
      */
-    @UnsupportedAppUsage
+
     public SatelliteManager(@Nullable Context context) {
         this(context, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
     }
@@ -129,7 +128,7 @@
      * {@link #requestIsSatelliteEnabled(Executor, OutcomeReceiver)}.
      * @hide
      */
-    @UnsupportedAppUsage
+
     public static final String KEY_SATELLITE_ENABLED = "satellite_enabled";
 
     /**
@@ -137,7 +136,7 @@
      * {@link #requestIsDemoModeEnabled(Executor, OutcomeReceiver)}.
      * @hide
      */
-    @UnsupportedAppUsage
+
     public static final String KEY_DEMO_MODE_ENABLED = "demo_mode_enabled";
 
     /**
@@ -145,7 +144,7 @@
      * {@link #requestIsSatelliteSupported(Executor, OutcomeReceiver)}.
      * @hide
      */
-    @UnsupportedAppUsage
+
     public static final String KEY_SATELLITE_SUPPORTED = "satellite_supported";
 
     /**
@@ -153,7 +152,7 @@
      * {@link #requestSatelliteCapabilities(Executor, OutcomeReceiver)}.
      * @hide
      */
-    @UnsupportedAppUsage
+
     public static final String KEY_SATELLITE_CAPABILITIES = "satellite_capabilities";
 
     /**
@@ -161,7 +160,7 @@
      * {@link #requestIsSatelliteProvisioned(Executor, OutcomeReceiver)}.
      * @hide
      */
-    @UnsupportedAppUsage
+
     public static final String KEY_SATELLITE_PROVISIONED = "satellite_provisioned";
 
     /**
@@ -169,7 +168,7 @@
      * {@link #requestIsSatelliteCommunicationAllowedForCurrentLocation(Executor, OutcomeReceiver)}.
      * @hide
      */
-    @UnsupportedAppUsage
+
     public static final String KEY_SATELLITE_COMMUNICATION_ALLOWED =
             "satellite_communication_allowed";
 
@@ -178,7 +177,7 @@
      * {@link #requestTimeForNextSatelliteVisibility(Executor, OutcomeReceiver)}.
      * @hide
      */
-    @UnsupportedAppUsage
+
     public static final String KEY_SATELLITE_NEXT_VISIBILITY = "satellite_next_visibility";
 
     /**
@@ -334,6 +333,46 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface NTRadioTechnology {}
 
+    /** Suggested device hold position is unknown. */
+    public static final int DEVICE_HOLD_POSITION_UNKNOWN = 0;
+    /** User is suggested to hold the device in portrait mode. */
+    public static final int DEVICE_HOLD_POSITION_PORTRAIT = 1;
+    /** User is suggested to hold the device in landscape mode with left hand. */
+    public static final int DEVICE_HOLD_POSITION_LANDSCAPE_LEFT = 2;
+    /** User is suggested to hold the device in landscape mode with right hand. */
+    public static final int DEVICE_HOLD_POSITION_LANDSCAPE_RIGHT = 3;
+
+    /** @hide */
+    @IntDef(prefix = {"DEVICE_HOLD_POSITION_"}, value = {
+            DEVICE_HOLD_POSITION_UNKNOWN,
+            DEVICE_HOLD_POSITION_PORTRAIT,
+            DEVICE_HOLD_POSITION_LANDSCAPE_LEFT,
+            DEVICE_HOLD_POSITION_LANDSCAPE_RIGHT
+       })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DeviceHoldPosition {}
+
+    /** Display mode is unknown. */
+    public static final int DISPLAY_MODE_UNKNOWN = 0;
+    /** Display mode of the device used for satellite communication for non-foldable phones. */
+    public static final int DISPLAY_MODE_FIXED = 1;
+    /** Display mode of the device used for satellite communication for foldabale phones when the
+     * device is opened. */
+    public static final int DISPLAY_MODE_OPENED = 2;
+    /** Display mode of the device used for satellite communication for foldabable phones when the
+     * device is closed. */
+    public static final int DISPLAY_MODE_CLOSED = 3;
+
+    /** @hide */
+    @IntDef(prefix = {"ANTENNA_POSITION_"}, value = {
+            DISPLAY_MODE_UNKNOWN,
+            DISPLAY_MODE_FIXED,
+            DISPLAY_MODE_OPENED,
+            DISPLAY_MODE_CLOSED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DisplayMode {}
+
     /**
      * Request to enable or disable the satellite modem and demo mode. If the satellite modem is
      * enabled, this may also disable the cellular modem, and if the satellite modem is disabled,
@@ -349,7 +388,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    @UnsupportedAppUsage
+
     public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
             @NonNull @CallbackExecutor Executor executor,
             @SatelliteError @NonNull Consumer<Integer> resultListener) {
@@ -392,7 +431,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    @UnsupportedAppUsage
+
     public void requestIsSatelliteEnabled(@NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
         Objects.requireNonNull(executor);
@@ -447,7 +486,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    @UnsupportedAppUsage
+
     public void requestIsDemoModeEnabled(@NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
         Objects.requireNonNull(executor);
@@ -500,7 +539,7 @@
      *
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
-    @UnsupportedAppUsage
+
     public void requestIsSatelliteSupported(@NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
         Objects.requireNonNull(executor);
@@ -554,7 +593,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    @UnsupportedAppUsage
+
     public void requestSatelliteCapabilities(@NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<SatelliteCapabilities, SatelliteException> callback) {
         Objects.requireNonNull(executor);
@@ -747,7 +786,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    @UnsupportedAppUsage
+
     public void startSatelliteTransmissionUpdates(@NonNull @CallbackExecutor Executor executor,
             @SatelliteError @NonNull Consumer<Integer> resultListener,
             @NonNull SatelliteTransmissionUpdateCallback callback) {
@@ -817,7 +856,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    @UnsupportedAppUsage
+
     public void stopSatelliteTransmissionUpdates(
             @NonNull SatelliteTransmissionUpdateCallback callback,
             @NonNull @CallbackExecutor Executor executor,
@@ -873,7 +912,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    @UnsupportedAppUsage
+
     public void provisionSatelliteService(@NonNull String token, @NonNull byte[] provisionData,
             @Nullable CancellationSignal cancellationSignal,
             @NonNull @CallbackExecutor Executor executor,
@@ -923,7 +962,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    @UnsupportedAppUsage
+
     public void deprovisionSatelliteService(@NonNull String token,
             @NonNull @CallbackExecutor Executor executor,
             @SatelliteError @NonNull Consumer<Integer> resultListener) {
@@ -963,7 +1002,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    @UnsupportedAppUsage
+
     @SatelliteError public int registerForSatelliteProvisionStateChanged(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull SatelliteProvisionStateCallback callback) {
@@ -1006,7 +1045,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    @UnsupportedAppUsage
+
     public void unregisterForSatelliteProvisionStateChanged(
             @NonNull SatelliteProvisionStateCallback callback) {
         Objects.requireNonNull(callback);
@@ -1045,7 +1084,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    @UnsupportedAppUsage
+
     public void requestIsSatelliteProvisioned(@NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
         Objects.requireNonNull(executor);
@@ -1097,7 +1136,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    @UnsupportedAppUsage
+
     @SatelliteError public int registerForSatelliteModemStateChanged(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull SatelliteStateCallback callback) {
@@ -1137,7 +1176,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    @UnsupportedAppUsage
+
     public void unregisterForSatelliteModemStateChanged(@NonNull SatelliteStateCallback callback) {
         Objects.requireNonNull(callback);
         ISatelliteStateCallback internalCallback = sSatelliteStateCallbackMap.remove(callback);
@@ -1172,7 +1211,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    @UnsupportedAppUsage
+
     @SatelliteError public int registerForSatelliteDatagram(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull SatelliteDatagramCallback callback) {
@@ -1228,7 +1267,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    @UnsupportedAppUsage
+
     public void unregisterForSatelliteDatagram(@NonNull SatelliteDatagramCallback callback) {
         Objects.requireNonNull(callback);
         ISatelliteDatagramCallback internalCallback =
@@ -1266,7 +1305,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    @UnsupportedAppUsage
+
     public void pollPendingSatelliteDatagrams(@NonNull @CallbackExecutor Executor executor,
             @SatelliteError @NonNull Consumer<Integer> resultListener) {
         Objects.requireNonNull(executor);
@@ -1319,7 +1358,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    @UnsupportedAppUsage
+
     public void sendSatelliteDatagram(@DatagramType int datagramType,
             @NonNull SatelliteDatagram datagram, boolean needFullScreenPointingUI,
             @NonNull @CallbackExecutor Executor executor,
@@ -1365,7 +1404,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    @UnsupportedAppUsage
+
     public void requestIsSatelliteCommunicationAllowedForCurrentLocation(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
@@ -1423,7 +1462,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    @UnsupportedAppUsage
+
     public void requestTimeForNextSatelliteVisibility(@NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<Duration, SatelliteException> callback) {
         Objects.requireNonNull(executor);
diff --git a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
index 20875af..a62eb8b 100644
--- a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
@@ -16,8 +16,6 @@
 
 package android.telephony.satellite;
 
-import android.compat.annotation.UnsupportedAppUsage;
-
 /**
  * A callback class for monitoring satellite provision state change events.
  *
@@ -30,6 +28,5 @@
      * @param provisioned The new provision state. {@code true} means satellite is provisioned
      *                    {@code false} means satellite is not provisioned.
      */
-    @UnsupportedAppUsage
     void onSatelliteProvisionStateChanged(boolean provisioned);
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
index 3312e3a..d9ecaa3 100644
--- a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
@@ -16,8 +16,6 @@
 
 package android.telephony.satellite;
 
-import android.compat.annotation.UnsupportedAppUsage;
-
 /**
  * A callback class for monitoring satellite modem state change events.
  *
@@ -28,6 +26,5 @@
      * Called when satellite modem state changes.
      * @param state The new satellite modem state.
      */
-    @UnsupportedAppUsage
     void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state);
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
index 17fff44..d4fe57a 100644
--- a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
@@ -17,7 +17,6 @@
 package android.telephony.satellite;
 
 import android.annotation.NonNull;
-import android.compat.annotation.UnsupportedAppUsage;
 
 /**
  * A callback class for monitoring satellite position update and datagram transfer state change
@@ -31,7 +30,6 @@
      *
      * @param pointingInfo The pointing info containing the satellite location.
      */
-    @UnsupportedAppUsage
     void onSatellitePositionChanged(@NonNull PointingInfo pointingInfo);
 
     /**
@@ -41,7 +39,6 @@
      * @param sendPendingCount The number of datagrams that are currently being sent.
      * @param errorCode If datagram transfer failed, the reason for failure.
      */
-    @UnsupportedAppUsage
     void onSendDatagramStateChanged(
             @SatelliteManager.SatelliteDatagramTransferState int state, int sendPendingCount,
             @SatelliteManager.SatelliteError int errorCode);
@@ -53,7 +50,6 @@
      * @param receivePendingCount The number of datagrams that are currently pending to be received.
      * @param errorCode If datagram transfer failed, the reason for failure.
      */
-    @UnsupportedAppUsage
     void onReceiveDatagramStateChanged(
             @SatelliteManager.SatelliteDatagramTransferState int state, int receivePendingCount,
             @SatelliteManager.SatelliteError int errorCode);
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl b/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl
index cd69da1..eaf96ab 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl
@@ -17,7 +17,7 @@
 package android.telephony.satellite.stub;
 
 import android.telephony.satellite.stub.NTRadioTechnology;
-
+import android.telephony.satellite.AntennaPosition;
 /**
  * {@hide}
  */
@@ -36,4 +36,14 @@
      * The maximum number of bytes per datagram that can be sent over satellite.
      */
     int maxBytesPerOutgoingDatagram;
+
+    /**
+     * Keys which are used to fill mAntennaPositionMap.
+     */
+    int[] antennaPositionKeys;
+
+    /**
+     * Antenna Position for different display modes received from satellite modem.
+     */
+    AntennaPosition[] antennaPositionValues;
 }
diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml
index f2ffc19..7272abb 100644
--- a/tests/FlickerTests/AndroidTest.xml
+++ b/tests/FlickerTests/AndroidTest.xml
@@ -29,6 +29,7 @@
         <option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" />
         <option name="teardown-command" value="settings delete system show_touches" />
         <option name="teardown-command" value="settings delete system pointer_location" />
+        <option name="teardown-command" value="cmd overlay enable com.android.internal.systemui.navbar.gestural" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true"/>
diff --git a/tests/HandwritingIme/src/com/google/android/test/handwritingime/BoundsInfoDrawHelper.java b/tests/HandwritingIme/src/com/google/android/test/handwritingime/BoundsInfoDrawHelper.java
new file mode 100644
index 0000000..6b924f3
--- /dev/null
+++ b/tests/HandwritingIme/src/com/google/android/test/handwritingime/BoundsInfoDrawHelper.java
@@ -0,0 +1,116 @@
+/*
+ * 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.google.android.test.handwritingime;
+
+import static com.google.android.test.handwritingime.HandwritingIme.BOUNDS_INFO_EDITOR_BOUNDS;
+import static com.google.android.test.handwritingime.HandwritingIme.BOUNDS_INFO_NONE;
+import static com.google.android.test.handwritingime.HandwritingIme.BOUNDS_INFO_VISIBLE_LINE_BOUNDS;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.view.View;
+import android.view.inputmethod.CursorAnchorInfo;
+import android.view.inputmethod.EditorBoundsInfo;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.graphics.ColorUtils;
+
+import java.util.List;
+
+public class BoundsInfoDrawHelper {
+    private static final Paint sPaint = new Paint();
+    private static final int EDITOR_BOUNDS_COLOR =
+            ColorUtils.setAlphaComponent(Color.DKGRAY, 128);
+    private static final int HANDWRITING_BOUNDS_COLOR =
+            ColorUtils.setAlphaComponent(Color.BLUE, 128);
+    private static final int VISIBLE_LINE_BOUNDS_COLOR =
+            ColorUtils.setAlphaComponent(Color.MAGENTA, 128);
+
+    public static void draw(Canvas canvas, View inkView, int boundsInfoMode,
+            CursorAnchorInfo cursorAnchorInfo) {
+        if (boundsInfoMode == BOUNDS_INFO_NONE || cursorAnchorInfo == null) {
+            return;
+        }
+
+        // The matrix in CursorAnchorInfo transforms the editor coordinates to on-screen
+        // coordinates. We then transform the matrix from the on-screen coordinates to the
+        // inkView's coordinates. So the result matrix transforms the editor coordinates
+        // to the inkView coordinates.
+        final Matrix matrix = cursorAnchorInfo.getMatrix();
+        inkView.transformMatrixToLocal(matrix);
+
+        if ((boundsInfoMode & BOUNDS_INFO_EDITOR_BOUNDS) != 0) {
+            drawEditorBoundsInfo(canvas, matrix, cursorAnchorInfo.getEditorBoundsInfo());
+        }
+
+        if ((boundsInfoMode & BOUNDS_INFO_VISIBLE_LINE_BOUNDS) != 0) {
+            drawVisibleLineBounds(canvas, matrix, cursorAnchorInfo.getVisibleLineBounds());
+        }
+    }
+
+    private static void setPaintForEditorBoundsInfo() {
+        sPaint.reset();
+        sPaint.setStyle(Paint.Style.STROKE);
+        sPaint.setStrokeWidth(5f);
+    }
+
+    private static void drawEditorBoundsInfo(Canvas canvas, Matrix matrix,
+            @Nullable EditorBoundsInfo editorBoundsInfo) {
+        if (editorBoundsInfo == null) {
+            return;
+        }
+        final RectF editorBounds = editorBoundsInfo.getEditorBounds();
+        setPaintForEditorBoundsInfo();
+        if (editorBounds != null) {
+            final RectF localEditorBounds = new RectF(editorBounds);
+            matrix.mapRect(localEditorBounds);
+            sPaint.setColor(EDITOR_BOUNDS_COLOR);
+            canvas.drawRect(localEditorBounds, sPaint);
+        }
+
+        final RectF handwritingBounds = editorBoundsInfo.getHandwritingBounds();
+        if (handwritingBounds != null) {
+            final RectF localHandwritingBounds = new RectF(handwritingBounds);
+            matrix.mapRect(localHandwritingBounds);
+            sPaint.setColor(HANDWRITING_BOUNDS_COLOR);
+            canvas.drawRect(localHandwritingBounds, sPaint);
+        }
+    }
+
+    private static void setPaintForVisibleLineBounds() {
+        sPaint.reset();
+        sPaint.setStyle(Paint.Style.STROKE);
+        sPaint.setStrokeWidth(2f);
+        sPaint.setColor(VISIBLE_LINE_BOUNDS_COLOR);
+    }
+
+    private static void drawVisibleLineBounds(Canvas canvas, Matrix matrix,
+            List<RectF> visibleLineBounds) {
+        if (visibleLineBounds.isEmpty()) {
+            return;
+        }
+        setPaintForVisibleLineBounds();
+        for (RectF lineBound : visibleLineBounds) {
+            matrix.mapRect(lineBound);
+            canvas.drawRect(lineBound, sPaint);
+        }
+    }
+}
diff --git a/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java b/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java
index 2fd2368..8380dcf 100644
--- a/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java
+++ b/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java
@@ -25,7 +25,9 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.Window;
+import android.view.inputmethod.CursorAnchorInfo;
 import android.view.inputmethod.DeleteGesture;
+import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.HandwritingGesture;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InsertGesture;
@@ -34,6 +36,7 @@
 import android.view.inputmethod.SelectGesture;
 import android.widget.AdapterView;
 import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 import android.widget.Spinner;
@@ -43,9 +46,6 @@
 import java.util.function.IntConsumer;
 
 public class HandwritingIme extends InputMethodService {
-
-    public static final int HEIGHT_DP = 100;
-
     private static final int OP_NONE = 0;
     private static final int OP_SELECT = 1;
     private static final int OP_DELETE = 2;
@@ -62,6 +62,12 @@
     private Spinner mRichGestureGranularitySpinner;
     private PointF mRichGestureStartPoint;
 
+    static final int BOUNDS_INFO_NONE = 0;
+    static final int BOUNDS_INFO_VISIBLE_LINE_BOUNDS = 1;
+    static final int BOUNDS_INFO_EDITOR_BOUNDS = 2;
+    private int mBoundsInfoMode = BOUNDS_INFO_NONE;
+    private LinearLayout mBoundsInfoCheckBoxes;
+
     private final IntConsumer mResultConsumer = value -> Log.d(TAG, "Gesture result: " + value);
 
     interface HandwritingFinisher {
@@ -201,12 +207,7 @@
     public View onCreateInputView() {
         Log.d(TAG, "onCreateInputView");
         final ViewGroup view = new FrameLayout(this);
-        final View inner = new View(this);
-        final float density = getResources().getDisplayMetrics().density;
-        final int height = (int) (HEIGHT_DP * density);
         view.setPadding(0, 0, 0, 0);
-        view.addView(inner, new FrameLayout.LayoutParams(
-                FrameLayout.LayoutParams.MATCH_PARENT, height));
 
         LinearLayout layout = new LinearLayout(this);
         layout.setLayoutParams(new LinearLayout.LayoutParams(
@@ -214,9 +215,9 @@
         layout.setOrientation(LinearLayout.VERTICAL);
         layout.addView(getRichGestureActionsSpinner());
         layout.addView(getRichGestureGranularitySpinner());
-
+        layout.addView(getBoundsInfoCheckBoxes());
+        layout.setBackgroundColor(getColor(R.color.holo_green_light));
         view.addView(layout);
-        inner.setBackgroundColor(getColor(R.color.holo_green_light));
 
         return view;
     }
@@ -228,7 +229,7 @@
         mRichGestureModeSpinner = new Spinner(this);
         mRichGestureModeSpinner.setPadding(100, 0, 100, 0);
         mRichGestureModeSpinner.setTooltipText("Handwriting IME mode");
-        String[] items = new String[] {
+        String[] items = new String[]{
                 "Handwriting IME - Rich gesture disabled",
                 "Rich gesture SELECT",
                 "Rich gesture DELETE",
@@ -259,6 +260,69 @@
         return mRichGestureModeSpinner;
     }
 
+    private void updateCursorAnchorInfo(int boundsInfoMode) {
+        final InputConnection ic = getCurrentInputConnection();
+        if (ic == null) return;
+
+        if (boundsInfoMode == BOUNDS_INFO_NONE) {
+            ic.requestCursorUpdates(0);
+            return;
+        }
+
+        final int cursorUpdateMode = InputConnection.CURSOR_UPDATE_MONITOR;
+        int cursorUpdateFilter = 0;
+        if ((boundsInfoMode & BOUNDS_INFO_EDITOR_BOUNDS) != 0) {
+            cursorUpdateFilter |= InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS;
+        }
+
+        if ((boundsInfoMode & BOUNDS_INFO_VISIBLE_LINE_BOUNDS) != 0) {
+            cursorUpdateFilter |= InputConnection.CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS;
+        }
+        ic.requestCursorUpdates(cursorUpdateMode | cursorUpdateFilter);
+    }
+
+    private void updateBoundsInfoMode() {
+        if (mInk != null) {
+            mInk.setBoundsInfoMode(mBoundsInfoMode);
+        }
+        updateCursorAnchorInfo(mBoundsInfoMode);
+    }
+
+    private View getBoundsInfoCheckBoxes() {
+        if (mBoundsInfoCheckBoxes != null) {
+            return mBoundsInfoCheckBoxes;
+        }
+        mBoundsInfoCheckBoxes = new LinearLayout(this);
+        mBoundsInfoCheckBoxes.setPadding(100, 0, 100, 0);
+        mBoundsInfoCheckBoxes.setOrientation(LinearLayout.HORIZONTAL);
+
+        final CheckBox editorBoundsInfoCheckBox = new CheckBox(this);
+        editorBoundsInfoCheckBox.setText("EditorBoundsInfo");
+        editorBoundsInfoCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
+            if (isChecked) {
+                mBoundsInfoMode |= BOUNDS_INFO_EDITOR_BOUNDS;
+            } else {
+                mBoundsInfoMode &= ~BOUNDS_INFO_EDITOR_BOUNDS;
+            }
+            updateBoundsInfoMode();
+        });
+
+        final CheckBox visibleLineBoundsInfoCheckBox = new CheckBox(this);
+        visibleLineBoundsInfoCheckBox.setText("VisibleLineBounds");
+        visibleLineBoundsInfoCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
+            if (isChecked) {
+                mBoundsInfoMode |= BOUNDS_INFO_VISIBLE_LINE_BOUNDS;
+            } else {
+                mBoundsInfoMode &= ~BOUNDS_INFO_VISIBLE_LINE_BOUNDS;
+            }
+            updateBoundsInfoMode();
+        });
+
+        mBoundsInfoCheckBoxes.addView(editorBoundsInfoCheckBox);
+        mBoundsInfoCheckBoxes.addView(visibleLineBoundsInfoCheckBox);
+        return mBoundsInfoCheckBoxes;
+    }
+
     private View getRichGestureGranularitySpinner() {
         if (mRichGestureGranularitySpinner != null) {
             return mRichGestureGranularitySpinner;
@@ -294,6 +358,7 @@
         Log.d(TAG, "onPrepareStylusHandwriting ");
         if (mInk == null) {
             mInk = new InkView(this, new HandwritingFinisherImpl(), new StylusConsumer());
+            mInk.setBoundsInfoMode(mBoundsInfoMode);
         }
     }
 
@@ -323,4 +388,16 @@
     private boolean areRichGesturesEnabled() {
         return mRichGestureMode != OP_NONE;
     }
+
+    @Override
+    public void onUpdateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo) {
+        if (mInk != null) {
+            mInk.setCursorAnchorInfo(cursorAnchorInfo);
+        }
+    }
+
+    @Override
+    public void onStartInput(EditorInfo attribute, boolean restarting) {
+        updateCursorAnchorInfo(mBoundsInfoMode);
+    }
 }
diff --git a/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java b/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java
index e94c79e..86b324c 100644
--- a/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java
+++ b/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java
@@ -26,6 +26,7 @@
 import android.view.ViewGroup;
 import android.view.WindowManager;
 import android.view.WindowMetrics;
+import android.view.inputmethod.CursorAnchorInfo;
 
 class InkView extends View {
     private static final long FINISH_TIMEOUT = 1500;
@@ -37,6 +38,9 @@
     private static final float STYLUS_MOVE_TOLERANCE = 1;
     private Runnable mFinishRunnable;
 
+    private CursorAnchorInfo mCursorAnchorInfo;
+    private int mBoundsInfoMode;
+
     InkView(Context context, HandwritingIme.HandwritingFinisher hwController,
             HandwritingIme.StylusConsumer consumer) {
         super(context);
@@ -66,6 +70,7 @@
 
         canvas.drawPath(mPath, mPaint);
         canvas.drawARGB(20, 255, 50, 50);
+        BoundsInfoDrawHelper.draw(canvas, this, mBoundsInfoMode, mCursorAnchorInfo);
     }
 
     private void stylusStart(float x, float y) {
@@ -156,4 +161,15 @@
         return mFinishRunnable;
     }
 
+    void setCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo) {
+        mCursorAnchorInfo = cursorAnchorInfo;
+        invalidate();
+    }
+
+    void setBoundsInfoMode(int boundsInfoMode) {
+        if (boundsInfoMode != mBoundsInfoMode) {
+            invalidate();
+        }
+        mBoundsInfoMode = boundsInfoMode;
+    }
 }
diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp
index a4c48fd..4fa6fbe 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -25,7 +25,7 @@
         "services.core.unboosted",
         "testables",
         "truth-prebuilt",
-        "ub-uiautomator",
+        "androidx.test.uiautomator_uiautomator",
     ],
     test_suites: ["device-tests"],
 }
diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt
index 0246426..d185ee6 100644
--- a/tests/Input/src/com/android/test/input/AnrTest.kt
+++ b/tests/Input/src/com/android/test/input/AnrTest.kt
@@ -27,14 +27,15 @@
 import android.os.SystemClock
 import android.provider.Settings
 import android.provider.Settings.Global.HIDE_ERROR_DIALOGS
-import android.support.test.uiautomator.By
-import android.support.test.uiautomator.UiDevice
-import android.support.test.uiautomator.UiObject2
-import android.support.test.uiautomator.Until
 import android.testing.PollingCheck
 import android.view.InputDevice
 import android.view.MotionEvent
 
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.Until
+
 import org.junit.After
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertTrue
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index f8d885a..d7fa124 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -417,9 +417,9 @@
                         int failureReason, int mitigationCount) {
                     if (versionedPackage.getVersionCode() == VERSION_CODE) {
                         // Only rollback for specific versionCode
-                        return PackageHealthObserverImpact.USER_IMPACT_MEDIUM;
+                        return PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
                     }
-                    return PackageHealthObserverImpact.USER_IMPACT_NONE;
+                    return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
                 }
             };
 
@@ -442,13 +442,13 @@
     public void testPackageFailureNotifyAllDifferentImpacts() throws Exception {
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observerNone = new TestObserver(OBSERVER_NAME_1,
-                PackageHealthObserverImpact.USER_IMPACT_NONE);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_0);
         TestObserver observerHigh = new TestObserver(OBSERVER_NAME_2,
-                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
         TestObserver observerMid = new TestObserver(OBSERVER_NAME_3,
-                PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
         TestObserver observerLow = new TestObserver(OBSERVER_NAME_4,
-                PackageHealthObserverImpact.USER_IMPACT_LOW);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
 
         // Start observing for all impact observers
         watchdog.startObservingHealth(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D),
@@ -499,9 +499,9 @@
     public void testPackageFailureNotifyLeastImpactSuccessively() throws Exception {
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observerFirst = new TestObserver(OBSERVER_NAME_1,
-                PackageHealthObserverImpact.USER_IMPACT_LOW);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
         TestObserver observerSecond = new TestObserver(OBSERVER_NAME_2,
-                PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
 
         // Start observing for observerFirst and observerSecond with failure handling
         watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION);
@@ -517,7 +517,7 @@
         assertThat(observerSecond.mMitigatedPackages).isEmpty();
 
         // After observerFirst handles failure, next action it has is high impact
-        observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_HIGH;
+        observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_100;
         observerFirst.mMitigatedPackages.clear();
         observerSecond.mMitigatedPackages.clear();
 
@@ -531,7 +531,7 @@
         assertThat(observerFirst.mMitigatedPackages).isEmpty();
 
         // After observerSecond handles failure, it has no further actions
-        observerSecond.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE;
+        observerSecond.mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
         observerFirst.mMitigatedPackages.clear();
         observerSecond.mMitigatedPackages.clear();
 
@@ -545,7 +545,7 @@
         assertThat(observerSecond.mMitigatedPackages).isEmpty();
 
         // After observerFirst handles failure, it too has no further actions
-        observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE;
+        observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
         observerFirst.mMitigatedPackages.clear();
         observerSecond.mMitigatedPackages.clear();
 
@@ -566,9 +566,9 @@
     public void testPackageFailureNotifyOneSameImpact() throws Exception {
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1,
-                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2,
-                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
 
         // Start observing for observer1 and observer2 with failure handling
         watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
@@ -592,11 +592,11 @@
         TestController controller = new TestController();
         PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */);
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1,
-                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2,
-                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
         TestObserver observer3 = new TestObserver(OBSERVER_NAME_3,
-                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
 
 
         // Start observing with explicit health checks for APP_A and APP_B respectively
@@ -645,7 +645,7 @@
         TestController controller = new TestController();
         PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */);
         TestObserver observer = new TestObserver(OBSERVER_NAME_1,
-                PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
 
         // Start observing with explicit health checks for APP_A and APP_B
         controller.setSupportedPackages(Arrays.asList(APP_A, APP_B, APP_C));
@@ -711,7 +711,7 @@
         TestController controller = new TestController();
         PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */);
         TestObserver observer = new TestObserver(OBSERVER_NAME_1,
-                PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
 
         // Start observing with explicit health checks for APP_A and
         // package observation duration == LONG_DURATION
@@ -742,7 +742,7 @@
         TestController controller = new TestController();
         PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */);
         TestObserver observer = new TestObserver(OBSERVER_NAME_1,
-                PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
 
         // Start observing with explicit health checks for APP_A and
         // package observation duration == SHORT_DURATION / 2
@@ -818,7 +818,7 @@
 
         // Start observing with failure handling
         TestObserver observer = new TestObserver(OBSERVER_NAME_1,
-                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+                PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
         wd.startObservingHealth(observer, Collections.singletonList(APP_A), SHORT_DURATION);
 
         // Notify of NetworkStack failure
@@ -1073,9 +1073,9 @@
     public void testBootLoopMitigationDoneForLowestUserImpact() {
         PackageWatchdog watchdog = createWatchdog();
         TestObserver bootObserver1 = new TestObserver(OBSERVER_NAME_1);
-        bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LOW);
+        bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
         TestObserver bootObserver2 = new TestObserver(OBSERVER_NAME_2);
-        bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
+        bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
         watchdog.registerHealthObserver(bootObserver1);
         watchdog.registerHealthObserver(bootObserver2);
         for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
@@ -1446,7 +1446,7 @@
 
         TestObserver(String name) {
             mName = name;
-            mImpact = PackageHealthObserverImpact.USER_IMPACT_MEDIUM;
+            mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
         }
 
         TestObserver(String name, int impact) {
diff --git a/tests/WindowAnimationJank/Android.bp b/tests/WindowAnimationJank/Android.bp
index ed86aa5..8542f88 100644
--- a/tests/WindowAnimationJank/Android.bp
+++ b/tests/WindowAnimationJank/Android.bp
@@ -25,7 +25,7 @@
     name: "WindowAnimationJank",
     srcs: ["src/**/*.java"],
     static_libs: [
-        "ub-uiautomator",
+        "androidx.test.uiautomator_uiautomator",
         "androidx.test.janktesthelper",
         "junit",
     ],
diff --git a/tests/WindowAnimationJank/src/android/windowanimationjank/Utils.java b/tests/WindowAnimationJank/src/android/windowanimationjank/Utils.java
index 2531464..48a359c 100644
--- a/tests/WindowAnimationJank/src/android/windowanimationjank/Utils.java
+++ b/tests/WindowAnimationJank/src/android/windowanimationjank/Utils.java
@@ -18,11 +18,12 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.os.SystemClock;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.BySelector;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObject2;
-import android.support.test.uiautomator.Until;
+
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
 
 /**
  * Set of helpers to manipulate test activities.
diff --git a/tests/WindowAnimationJank/src/android/windowanimationjank/WindowAnimationJankTestBase.java b/tests/WindowAnimationJank/src/android/windowanimationjank/WindowAnimationJankTestBase.java
index a8ace162..cb7c511 100644
--- a/tests/WindowAnimationJank/src/android/windowanimationjank/WindowAnimationJankTestBase.java
+++ b/tests/WindowAnimationJank/src/android/windowanimationjank/WindowAnimationJankTestBase.java
@@ -16,9 +16,8 @@
 
 package android.windowanimationjank;
 
-import android.support.test.uiautomator.UiDevice;
-
 import androidx.test.jank.JankTestBase;
+import androidx.test.uiautomator.UiDevice;
 
 /**
  * This adds additional system level jank monitor and its result is merged with primary monitor
diff --git a/tests/testables/src/android/testing/TestableSettingsProvider.java b/tests/testables/src/android/testing/TestableSettingsProvider.java
index c6f18fd..b850cb8 100644
--- a/tests/testables/src/android/testing/TestableSettingsProvider.java
+++ b/tests/testables/src/android/testing/TestableSettingsProvider.java
@@ -72,7 +72,11 @@
 
     public Bundle call(String method, String arg, Bundle extras) {
         // Methods are "GET_system", "GET_global", "PUT_secure", etc.
-        final int userId = extras.getInt(Settings.CALL_METHOD_USER_KEY, UserHandle.myUserId());
+        int userId = extras.getInt(Settings.CALL_METHOD_USER_KEY, UserHandle.USER_CURRENT);
+        if (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF) {
+            userId = UserHandle.myUserId();
+        }
+
         final String[] commands = method.split("_", 2);
         final String op = commands[0];
         final String table = commands[1];
diff --git a/tests/testables/tests/AndroidManifest.xml b/tests/testables/tests/AndroidManifest.xml
index 2bfb04f..1731f6b 100644
--- a/tests/testables/tests/AndroidManifest.xml
+++ b/tests/testables/tests/AndroidManifest.xml
@@ -21,7 +21,7 @@
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
     <uses-permission android:name="android.permission.MANAGE_USERS" />
 
-    <application android:debuggable="true">
+    <application android:debuggable="true" android:testOnly="true">
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/tests/testables/tests/AndroidTest.xml b/tests/testables/tests/AndroidTest.xml
new file mode 100644
index 0000000..6d29794
--- /dev/null
+++ b/tests/testables/tests/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<configuration description="Runs Testable Tests.">
+    <option name="test-tag" value="TestablesTests" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="install-arg" value="-t" />
+        <option name="test-file-name" value="TestablesTests.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="com.android.testables"/>
+    </test>
+</configuration>
diff --git a/tools/aapt/SdkConstants.h b/tools/aapt/SdkConstants.h
index a146466..e2c1614 100644
--- a/tools/aapt/SdkConstants.h
+++ b/tools/aapt/SdkConstants.h
@@ -49,6 +49,7 @@
     SDK_S = 31,
     SDK_S_V2 = 32,
     SDK_TIRAMISU = 33,
+    SDK_UPSIDE_DOWN_CAKE = 34,
     SDK_CUR_DEVELOPMENT = 10000,
 };
 
diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h
index 40bcef7..e47704ea 100644
--- a/tools/aapt2/SdkConstants.h
+++ b/tools/aapt2/SdkConstants.h
@@ -59,6 +59,7 @@
   SDK_S = 31,
   SDK_S_V2 = 32,
   SDK_TIRAMISU = 33,
+  SDK_UPSIDE_DOWN_CAKE = 34,
   SDK_CUR_DEVELOPMENT = 10000,
 };
 
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
index 25fbabc..166fbdd 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
@@ -163,11 +163,11 @@
         /**
          * Sets the displayed connection strength of the remote device to the internet.
          *
-         * @param connectionStrength Connection strength in range 0 to 3.
+         * @param connectionStrength Connection strength in range 0 to 4.
          * @return Returns the Builder object.
          */
         @NonNull
-        public Builder setConnectionStrength(@IntRange(from = 0, to = 3) int connectionStrength) {
+        public Builder setConnectionStrength(@IntRange(from = 0, to = 4) int connectionStrength) {
             mConnectionStrength = connectionStrength;
             return this;
         }
@@ -205,8 +205,8 @@
         if (batteryPercentage < 0 || batteryPercentage > 100) {
             throw new IllegalArgumentException("BatteryPercentage must be in range 0-100");
         }
-        if (connectionStrength < 0 || connectionStrength > 3) {
-            throw new IllegalArgumentException("ConnectionStrength must be in range 0-3");
+        if (connectionStrength < 0 || connectionStrength > 4) {
+            throw new IllegalArgumentException("ConnectionStrength must be in range 0-4");
         }
     }
 
@@ -265,9 +265,9 @@
     /**
      * Gets the displayed connection strength of the remote device to the internet.
      *
-     * @return Returns the connection strength in range 0 to 3.
+     * @return Returns the connection strength in range 0 to 4.
      */
-    @IntRange(from = 0, to = 3)
+    @IntRange(from = 0, to = 4)
     public int getConnectionStrength() {
         return mConnectionStrength;
     }