Merge "Request embedded focus if a SurfacePackage was added to SV" into udc-dev
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 3aec8ba..6eeff82 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -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/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/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 2aead3c..72a3f6c 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -51,9 +51,11 @@
 import android.view.Surface;
 
 import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.Executor;
@@ -81,8 +83,10 @@
     private final DisplayManagerGlobal mGlobal;
 
     private final Object mLock = new Object();
-    private final SparseArray<Display> mDisplays = new SparseArray<Display>();
+    @GuardedBy("mLock")
+    private final WeakDisplayCache mDisplayCache = new WeakDisplayCache();
 
+    @GuardedBy("mLock")
     private final ArrayList<Display> mTempDisplays = new ArrayList<Display>();
 
     /**
@@ -684,6 +688,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private void addAllDisplaysLocked(ArrayList<Display> displays, int[] displayIds) {
         for (int i = 0; i < displayIds.length; i++) {
             Display display = getOrCreateDisplayLocked(displayIds[i], true /*assumeValid*/);
@@ -693,6 +698,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private void addDisplaysLocked(
             ArrayList<Display> displays, int[] displayIds, int matchType, int flagMask) {
         for (int displayId : displayIds) {
@@ -709,8 +715,9 @@
         }
     }
 
+    @GuardedBy("mLock")
     private Display getOrCreateDisplayLocked(int displayId, boolean assumeValid) {
-        Display display = mDisplays.get(displayId);
+        Display display = mDisplayCache.get(displayId);
         if (display == null) {
             // TODO: We cannot currently provide any override configurations for metrics on displays
             // other than the display the context is associated with.
@@ -719,7 +726,7 @@
 
             display = mGlobal.getCompatibleDisplay(displayId, resources);
             if (display != null) {
-                mDisplays.put(displayId, display);
+                mDisplayCache.put(display);
             }
         } else if (!assumeValid && !display.isValid()) {
             display = null;
@@ -1767,4 +1774,57 @@
          */
         String KEY_BRIGHTNESS_THROTTLING_DATA = "brightness_throttling_data";
     }
+
+    /**
+     * Helper class to maintain cache of weak references to Display instances.
+     *
+     * Note this class is not thread-safe, so external synchronization is needed if accessed
+     * concurrently.
+     */
+    private static final class WeakDisplayCache {
+        private final SparseArray<WeakReference<Display>> mDisplayCache = new SparseArray<>();
+
+        /**
+         * Return cached {@link Display} instance for the provided display id.
+         *
+         * @param displayId - display id of the requested {@link Display} instance.
+         * @return cached {@link Display} instance or null
+         */
+        Display get(int displayId) {
+            WeakReference<Display> wrDisplay = mDisplayCache.get(displayId);
+            if (wrDisplay == null) {
+                return null;
+            }
+            return wrDisplay.get();
+        }
+
+        /**
+         * Insert new {@link Display} instance in the cache. This replaced the previously cached
+         * {@link Display} instance, if there's already one with the same display id.
+         *
+         * @param display - Display instance to cache.
+         */
+        void put(Display display) {
+            removeStaleEntries();
+            mDisplayCache.put(display.getDisplayId(), new WeakReference<>(display));
+        }
+
+        /**
+         * Evict gc-ed entries from the cache.
+         */
+        private void removeStaleEntries() {
+            ArrayList<Integer> staleEntriesIndices = new ArrayList();
+            for (int i = 0; i < mDisplayCache.size(); i++) {
+                if (mDisplayCache.valueAt(i).get() == null) {
+                    staleEntriesIndices.add(i);
+                }
+            }
+
+            for (int i = 0; i < staleEntriesIndices.size(); i++) {
+                // removeAt call to SparseArray doesn't compact the underlying array
+                // so the indices stay valid even after removal.
+                mDisplayCache.removeAt(staleEntriesIndices.get(i));
+            }
+        }
+    }
 }
diff --git a/core/java/android/os/image/DynamicSystemClient.java b/core/java/android/os/image/DynamicSystemClient.java
index ecce2ff..218ecc8 100644
--- a/core/java/android/os/image/DynamicSystemClient.java
+++ b/core/java/android/os/image/DynamicSystemClient.java
@@ -238,6 +238,15 @@
      */
     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/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/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 77bbeb5..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;
 
 /**
@@ -431,6 +432,7 @@
                 Message msg = mCaller.obtainMessageIO(MSG_WINDOW_RESIZED,
                         reportDraw ? 1 : 0,
                         mergedConfiguration);
+                mIWallpaperEngine.mPendingResizeCount.incrementAndGet();
                 mCaller.sendMessage(msg);
             }
 
@@ -510,6 +512,7 @@
         public Engine(Supplier<Long> clockFunction, Handler handler) {
             mClockFunction = clockFunction;
             mHandler = handler;
+            mMergedConfiguration.setOverrideConfiguration(getResources().getConfiguration());
         }
 
         /**
@@ -1051,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);
@@ -1113,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");
@@ -1165,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;
@@ -1221,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);
@@ -2324,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;
@@ -2579,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?
@@ -2631,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/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 5dd2d82..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();
 
@@ -860,7 +860,7 @@
                 mFrameScheduled = false;
                 mLastFrameTimeNanos = frameTimeNanos;
                 mLastFrameIntervalNanos = frameIntervalNanos;
-                mLastVsyncEventData = vsyncEventData;
+                mLastVsyncEventData.copyFrom(vsyncEventData);
             }
 
             if (resynced && Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
@@ -1262,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);
@@ -1302,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/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 16c1335..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
@@ -307,7 +309,7 @@
         mWm = wwm;
         mViewRoot = new ViewRootImpl(c, d, mWm, new WindowlessWindowLayout());
         mCloseGuard.openWithCallSite("release", callsite);
-        addConfigCallback(c, d);
+        setConfigCallback(c, d);
 
         WindowManagerGlobal.getInstance().addWindowlessRoot(mViewRoot);
 
@@ -357,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);
     }
 
     /**
@@ -386,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 */);
     }
 
     /**
@@ -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();
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 055b5cb..86e7fb0 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -11263,13 +11263,19 @@
                 }
 
                 if (syncBuffer) {
-                    mBlastBufferQueue.syncNextTransaction(new Consumer<Transaction>() {
-                        @Override
-                        public void accept(Transaction transaction) {
-                            surfaceSyncGroup.addTransaction(transaction);
-                            surfaceSyncGroup.markSyncReady();
-                        }
+                    boolean result = mBlastBufferQueue.syncNextTransaction(transaction -> {
+                        surfaceSyncGroup.addTransaction(transaction);
+                        surfaceSyncGroup.markSyncReady();
                     });
+                    if (!result) {
+                        // syncNextTransaction can only return false if something is already trying
+                        // to sync the same frame in the same BBQ. That shouldn't be possible, but
+                        // if it did happen, invoke markSyncReady so the active SSG doesn't get
+                        // stuck.
+                        Log.e(mTag, "Unable to syncNextTransaction. Possibly something else is"
+                                + " trying to sync?");
+                        surfaceSyncGroup.markSyncReady();
+                    }
                 }
 
                 return didProduceBuffer -> {
@@ -11283,7 +11289,7 @@
                     // the next draw attempt. The next transaction and transaction complete callback
                     // were only set for the current draw attempt.
                     if (!didProduceBuffer) {
-                        mBlastBufferQueue.syncNextTransaction(null);
+                        mBlastBufferQueue.clearSyncTransaction();
 
                         // Gather the transactions that were sent to mergeWithNextTransaction
                         // since the frame didn't draw on this vsync. It's possible the frame will
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index 7d1dc76..84ef226 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -28,6 +28,7 @@
 import android.os.SystemClock;
 import android.util.AttributeSet;
 import android.util.Xml;
+import android.view.InflateException;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -137,16 +138,9 @@
         try {
             parser = context.getResources().getAnimation(id);
             return createAnimationFromXml(context, parser);
-        } catch (XmlPullParserException ex) {
-            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
-                    Integer.toHexString(id));
-            rnf.initCause(ex);
-            throw rnf;
-        } catch (IOException ex) {
-            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
-                    Integer.toHexString(id));
-            rnf.initCause(ex);
-            throw rnf;
+        } catch (XmlPullParserException | IOException ex) {
+            throw new NotFoundException(
+                    "Can't load animation resource ID #0x" + Integer.toHexString(id), ex);
         } finally {
             if (parser != null) parser.close();
         }
@@ -159,8 +153,9 @@
     }
 
     @UnsupportedAppUsage
-    private static Animation createAnimationFromXml(Context c, XmlPullParser parser,
-            AnimationSet parent, AttributeSet attrs) throws XmlPullParserException, IOException {
+    private static Animation createAnimationFromXml(
+            Context c, XmlPullParser parser, AnimationSet parent, AttributeSet attrs)
+            throws XmlPullParserException, IOException, InflateException {
 
         Animation anim = null;
 
@@ -168,8 +163,8 @@
         int type;
         int depth = parser.getDepth();
 
-        while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
-               && type != XmlPullParser.END_DOCUMENT) {
+        while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+                && type != XmlPullParser.END_DOCUMENT) {
 
             if (type != XmlPullParser.START_TAG) {
                 continue;
@@ -193,7 +188,7 @@
             } else if (name.equals("extend")) {
                 anim = new ExtendAnimation(c, attrs);
             } else {
-                throw new RuntimeException("Unknown animation name: " + parser.getName());
+                throw new InflateException("Unknown animation name: " + parser.getName());
             }
 
             if (parent != null) {
@@ -220,29 +215,24 @@
         try {
             parser = context.getResources().getAnimation(id);
             return createLayoutAnimationFromXml(context, parser);
-        } catch (XmlPullParserException ex) {
-            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
-                    Integer.toHexString(id));
-            rnf.initCause(ex);
-            throw rnf;
-        } catch (IOException ex) {
-            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
-                    Integer.toHexString(id));
-            rnf.initCause(ex);
-            throw rnf;
+        } catch (XmlPullParserException | IOException | InflateException ex) {
+            throw new NotFoundException(
+                    "Can't load animation resource ID #0x" + Integer.toHexString(id), ex);
         } finally {
             if (parser != null) parser.close();
         }
     }
 
-    private static LayoutAnimationController createLayoutAnimationFromXml(Context c,
-            XmlPullParser parser) throws XmlPullParserException, IOException {
+    private static LayoutAnimationController createLayoutAnimationFromXml(
+            Context c, XmlPullParser parser)
+            throws XmlPullParserException, IOException, InflateException {
 
         return createLayoutAnimationFromXml(c, parser, Xml.asAttributeSet(parser));
     }
 
-    private static LayoutAnimationController createLayoutAnimationFromXml(Context c,
-            XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException {
+    private static LayoutAnimationController createLayoutAnimationFromXml(
+            Context c, XmlPullParser parser, AttributeSet attrs)
+            throws XmlPullParserException, IOException, InflateException {
 
         LayoutAnimationController controller = null;
 
@@ -263,7 +253,7 @@
             } else if ("gridLayoutAnimation".equals(name)) {
                 controller = new GridLayoutAnimationController(c, attrs);
             } else {
-                throw new RuntimeException("Unknown layout animation name: " + name);
+                throw new InflateException("Unknown layout animation name: " + name);
             }
         }
 
@@ -342,16 +332,9 @@
         try {
             parser = context.getResources().getAnimation(id);
             return createInterpolatorFromXml(context.getResources(), context.getTheme(), parser);
-        } catch (XmlPullParserException ex) {
-            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
-                    Integer.toHexString(id));
-            rnf.initCause(ex);
-            throw rnf;
-        } catch (IOException ex) {
-            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
-                    Integer.toHexString(id));
-            rnf.initCause(ex);
-            throw rnf;
+        } catch (XmlPullParserException | IOException | InflateException ex) {
+            throw new NotFoundException(
+                    "Can't load animation resource ID #0x" + Integer.toHexString(id), ex);
         } finally {
             if (parser != null) parser.close();
         }
@@ -367,30 +350,26 @@
      * @throws NotFoundException
      * @hide
      */
-    public static Interpolator loadInterpolator(Resources res, Theme theme, int id) throws NotFoundException {
+    public static Interpolator loadInterpolator(Resources res, Theme theme, int id)
+            throws NotFoundException {
         XmlResourceParser parser = null;
         try {
             parser = res.getAnimation(id);
             return createInterpolatorFromXml(res, theme, parser);
-        } catch (XmlPullParserException ex) {
-            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
-                    Integer.toHexString(id));
-            rnf.initCause(ex);
-            throw rnf;
-        } catch (IOException ex) {
-            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
-                    Integer.toHexString(id));
-            rnf.initCause(ex);
-            throw rnf;
+        } catch (XmlPullParserException | IOException | InflateException ex) {
+            throw new NotFoundException(
+                    "Can't load animation resource ID #0x" + Integer.toHexString(id), ex);
         } finally {
-            if (parser != null)
+            if (parser != null) {
                 parser.close();
+            }
         }
 
     }
 
-    private static Interpolator createInterpolatorFromXml(Resources res, Theme theme, XmlPullParser parser)
-            throws XmlPullParserException, IOException {
+    private static Interpolator createInterpolatorFromXml(
+            Resources res, Theme theme, XmlPullParser parser)
+            throws XmlPullParserException, IOException, InflateException {
 
         BaseInterpolator interpolator = null;
 
@@ -430,7 +409,7 @@
             } else if (name.equals("pathInterpolator")) {
                 interpolator = new PathInterpolator(res, theme, attrs);
             } else {
-                throw new RuntimeException("Unknown interpolator name: " + parser.getName());
+                throw new InflateException("Unknown interpolator name: " + parser.getName());
             }
         }
         return interpolator;
diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java
index 7f99fb7..b3d5124 100644
--- a/core/java/android/window/SurfaceSyncGroup.java
+++ b/core/java/android/window/SurfaceSyncGroup.java
@@ -120,6 +120,7 @@
     private static HandlerThread sHandlerThread;
     private Handler mHandler;
 
+    @GuardedBy("mLock")
     private boolean mTimeoutAdded;
 
     private static boolean isLocalBinder(IBinder binder) {
@@ -234,6 +235,9 @@
      * SurfaceSyncGroup have completed their sync.
      */
     public void markSyncReady() {
+        if (DEBUG) {
+            Log.d(TAG, "markSyncReady " + mName);
+        }
         if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
             Trace.traceBegin(Trace.TRACE_TAG_VIEW, "markSyncReady " + mName);
         }
@@ -456,7 +460,15 @@
      */
     public void addTransaction(@NonNull Transaction transaction) {
         synchronized (mLock) {
-            mTransaction.merge(transaction);
+            // If the caller tries to add a transaction to a completed SSG, just apply the
+            // transaction immediately since there's nothing to wait on.
+            if (mFinished) {
+                Log.w(TAG, "Adding transaction to a completed SurfaceSyncGroup(" + mName + "). "
+                        + " Applying immediately");
+                transaction.apply();
+            } else {
+                mTransaction.merge(transaction);
+            }
         }
     }
 
@@ -509,7 +521,7 @@
 
     private boolean addLocalSync(ISurfaceSyncGroup childSyncToken, boolean parentSyncGroupMerge) {
         if (DEBUG) {
-            Log.d(TAG, "Adding local sync " + mName);
+            Log.d(TAG, "Adding local sync to " + mName);
         }
 
         SurfaceSyncGroup childSurfaceSyncGroup = getSurfaceSyncGroup(childSyncToken);
@@ -540,7 +552,7 @@
     private void setTransactionCallbackFromParent(ISurfaceSyncGroup parentSyncGroup,
             ITransactionReadyCallback transactionReadyCallback) {
         if (DEBUG) {
-            Log.d(TAG, "setTransactionCallbackFromParent " + mName);
+            Log.d(TAG, "setTransactionCallbackFromParent for child " + mName);
         }
 
         if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
@@ -677,7 +689,7 @@
      */
     public ITransactionReadyCallback createTransactionReadyCallback(boolean parentSyncGroupMerge) {
         if (DEBUG) {
-            Log.d(TAG, "createTransactionReadyCallback " + mName);
+            Log.d(TAG, "createTransactionReadyCallback as part of " + mName);
         }
         ITransactionReadyCallback transactionReadyCallback =
                 new ITransactionReadyCallback.Stub() {
@@ -780,7 +792,7 @@
 
         Runnable runnable = () -> {
             Log.e(TAG, "Failed to receive transaction ready in " + TRANSACTION_READY_TIMEOUT
-                    + "ms. Marking SurfaceSyncGroup as ready " + mName);
+                    + "ms. Marking SurfaceSyncGroup(" + mName + ") as ready");
             // Clear out any pending syncs in case the other syncs can't complete or timeout due to
             // a crash.
             synchronized (mLock) {
diff --git a/core/java/com/android/internal/app/OWNERS b/core/java/com/android/internal/app/OWNERS
index a1d571f..52f18fb 100644
--- a/core/java/com/android/internal/app/OWNERS
+++ b/core/java/com/android/internal/app/OWNERS
@@ -1,15 +1,16 @@
 per-file *AppOp* = file:/core/java/android/permission/OWNERS
 per-file UnlaunchableAppActivity.java = file:/core/java/android/app/admin/WorkProfile_OWNERS
 per-file IntentForwarderActivity.java = file:/core/java/android/app/admin/WorkProfile_OWNERS
-per-file *Resolver* = file:/packages/SystemUI/OWNERS
-per-file *Chooser* = file:/packages/SystemUI/OWNERS
-per-file SimpleIconFactory.java = file:/packages/SystemUI/OWNERS
-per-file AbstractMultiProfilePagerAdapter.java = file:/packages/SystemUI/OWNERS
-per-file *EmptyStateProvider.java = file:/packages/SystemUI/OWNERS
 per-file NetInitiatedActivity.java = file:/location/java/android/location/OWNERS
 per-file *BatteryStats* = file:/BATTERY_STATS_OWNERS
 per-file *SoundTrigger* = file:/media/java/android/media/soundtrigger/OWNERS
 
+# Chooser and Resolver.
+per-file *Chooser* = file:chooser/OWNERS
+per-file *Resolver* = file:chooser/OWNERS
+per-file SimpleIconFactory.java = file:chooser/OWNERS
+per-file AbstractMultiProfilePagerAdapter.java = file:chooser/OWNERS
+per-file *EmptyStateProvider.java = file:chooser/OWNERS
 
 # Voice Interaction
 per-file *Assist* = file:/core/java/android/service/voice/OWNERS
diff --git a/core/java/com/android/internal/app/chooser/OWNERS b/core/java/com/android/internal/app/chooser/OWNERS
index a6f1632..0844cfa 100644
--- a/core/java/com/android/internal/app/chooser/OWNERS
+++ b/core/java/com/android/internal/app/chooser/OWNERS
@@ -1 +1,3 @@
-file:/packages/SystemUI/OWNERS
\ No newline at end of file
+# Bug component: 324112
+
+include platform/packages/modules/IntentResolver:/OWNERS
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index cee8c1a..3633d91 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -1018,7 +1018,7 @@
      * Applies debugger system properties to the zygote arguments.
      *
      * For eng builds all apps are debuggable. On userdebug and user builds
-     * if persist.debuggable.dalvik.vm.jdwp.enabled is 1 all apps are
+     * if persist.debug.dalvik.vm.jdwp.enabled is 1 all apps are
      * debuggable. Otherwise, the debugger state is specified via the
      * "--enable-jdwp" flag in the spawn request.
      *
diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java
index 1172f7b..aa2318d 100644
--- a/core/java/com/android/internal/policy/TransitionAnimation.java
+++ b/core/java/com/android/internal/policy/TransitionAnimation.java
@@ -50,6 +50,7 @@
 import android.media.ImageReader;
 import android.os.SystemProperties;
 import android.util.Slog;
+import android.view.InflateException;
 import android.view.SurfaceControl;
 import android.view.WindowManager.LayoutParams;
 import android.view.WindowManager.TransitionOldType;
@@ -1264,7 +1265,7 @@
     public static Animation loadAnimationSafely(Context context, int resId, String tag) {
         try {
             return AnimationUtils.loadAnimation(context, resId);
-        } catch (Resources.NotFoundException e) {
+        } catch (Resources.NotFoundException | InflateException e) {
             Slog.w(tag, "Unable to load animation resource", e);
             return null;
         }
diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp
index 0381510..55aa711 100644
--- a/core/jni/android_graphics_BLASTBufferQueue.cpp
+++ b/core/jni/android_graphics_BLASTBufferQueue.cpp
@@ -125,26 +125,24 @@
     jobject mObject;
 };
 
-static void nativeSyncNextTransaction(JNIEnv* env, jclass clazz, jlong ptr, jobject callback,
+static bool nativeSyncNextTransaction(JNIEnv* env, jclass clazz, jlong ptr, jobject callback,
                                       jboolean acquireSingleBuffer) {
+    LOG_ALWAYS_FATAL_IF(!callback, "callback passed in to syncNextTransaction must not be NULL");
+
     sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr);
     JavaVM* vm = nullptr;
     LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM");
-    if (!callback) {
-        queue->syncNextTransaction(nullptr, acquireSingleBuffer);
-    } else {
-        auto globalCallbackRef =
-                std::make_shared<JGlobalRefHolder>(vm, env->NewGlobalRef(callback));
-        queue->syncNextTransaction(
-                [globalCallbackRef](SurfaceComposerClient::Transaction* t) {
-                    JNIEnv* env = getenv(globalCallbackRef->vm());
-                    env->CallVoidMethod(globalCallbackRef->object(), gTransactionConsumer.accept,
-                                        env->NewObject(gTransactionClassInfo.clazz,
-                                                       gTransactionClassInfo.ctor,
-                                                       reinterpret_cast<jlong>(t)));
-                },
-                acquireSingleBuffer);
-    }
+
+    auto globalCallbackRef = std::make_shared<JGlobalRefHolder>(vm, env->NewGlobalRef(callback));
+    return queue->syncNextTransaction(
+            [globalCallbackRef](SurfaceComposerClient::Transaction* t) {
+                JNIEnv* env = getenv(globalCallbackRef->vm());
+                env->CallVoidMethod(globalCallbackRef->object(), gTransactionConsumer.accept,
+                                    env->NewObject(gTransactionClassInfo.clazz,
+                                                   gTransactionClassInfo.ctor,
+                                                   reinterpret_cast<jlong>(t)));
+            },
+            acquireSingleBuffer);
 }
 
 static void nativeStopContinuousSyncTransaction(JNIEnv* env, jclass clazz, jlong ptr) {
@@ -152,6 +150,11 @@
     queue->stopContinuousSyncTransaction();
 }
 
+static void nativeClearSyncTransaction(JNIEnv* env, jclass clazz, jlong ptr) {
+    sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr);
+    queue->clearSyncTransaction();
+}
+
 static void nativeUpdate(JNIEnv* env, jclass clazz, jlong ptr, jlong surfaceControl, jlong width,
                          jlong height, jint format) {
     sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr);
@@ -207,8 +210,9 @@
         {"nativeCreate", "(Ljava/lang/String;Z)J", (void*)nativeCreate},
         {"nativeGetSurface", "(JZ)Landroid/view/Surface;", (void*)nativeGetSurface},
         {"nativeDestroy", "(J)V", (void*)nativeDestroy},
-        {"nativeSyncNextTransaction", "(JLjava/util/function/Consumer;Z)V", (void*)nativeSyncNextTransaction},
+        {"nativeSyncNextTransaction", "(JLjava/util/function/Consumer;Z)Z", (void*)nativeSyncNextTransaction},
         {"nativeStopContinuousSyncTransaction", "(J)V", (void*)nativeStopContinuousSyncTransaction},
+        {"nativeClearSyncTransaction", "(J)V", (void*)nativeClearSyncTransaction},
         {"nativeUpdate", "(JJJJI)V", (void*)nativeUpdate},
         {"nativeMergeWithNextTransaction", "(JJJ)V", (void*)nativeMergeWithNextTransaction},
         {"nativeGetLastAcquiredFrameNum", "(J)J", (void*)nativeGetLastAcquiredFrameNum},
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/res/res/values/config.xml b/core/res/res/values/config.xml
index 9f10ae60..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 -->
@@ -4304,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/symbols.xml b/core/res/res/values/symbols.xml
index a823d1f..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" />
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/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/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java
index 9940ca3..c52f700 100644
--- a/graphics/java/android/graphics/BLASTBufferQueue.java
+++ b/graphics/java/android/graphics/BLASTBufferQueue.java
@@ -16,6 +16,7 @@
 
 package android.graphics;
 
+import android.annotation.NonNull;
 import android.view.Surface;
 import android.view.SurfaceControl;
 
@@ -31,9 +32,10 @@
     private static native long nativeCreate(String name, boolean updateDestinationFrame);
     private static native void nativeDestroy(long ptr);
     private static native Surface nativeGetSurface(long ptr, boolean includeSurfaceControlHandle);
-    private static native void nativeSyncNextTransaction(long ptr,
+    private static native boolean nativeSyncNextTransaction(long ptr,
             Consumer<SurfaceControl.Transaction> callback, boolean acquireSingleBuffer);
     private static native void nativeStopContinuousSyncTransaction(long ptr);
+    private static native void nativeClearSyncTransaction(long ptr);
     private static native void nativeUpdate(long ptr, long surfaceControl, long width, long height,
             int format);
     private static native void nativeMergeWithNextTransaction(long ptr, long transactionPtr,
@@ -92,9 +94,9 @@
      *                            acquired. If false, continue to acquire all buffers into the
      *                            transaction until stopContinuousSyncTransaction is called.
      */
-    public void syncNextTransaction(boolean acquireSingleBuffer,
-            Consumer<SurfaceControl.Transaction> callback) {
-        nativeSyncNextTransaction(mNativeObject, callback, acquireSingleBuffer);
+    public boolean syncNextTransaction(boolean acquireSingleBuffer,
+            @NonNull Consumer<SurfaceControl.Transaction> callback) {
+        return nativeSyncNextTransaction(mNativeObject, callback, acquireSingleBuffer);
     }
 
     /**
@@ -104,8 +106,8 @@
      * @param callback The callback invoked when the buffer has been added to the transaction. The
      *                 callback will contain the transaction with the buffer.
      */
-    public void syncNextTransaction(Consumer<SurfaceControl.Transaction> callback) {
-        syncNextTransaction(true /* acquireSingleBuffer */, callback);
+    public boolean syncNextTransaction(@NonNull Consumer<SurfaceControl.Transaction> callback) {
+        return syncNextTransaction(true /* acquireSingleBuffer */, callback);
     }
 
     /**
@@ -118,6 +120,14 @@
     }
 
     /**
+     * Tell BBQ to clear the sync transaction that was previously set. The callback will not be
+     * invoked when the next frame is acquired.
+     */
+    public void clearSyncTransaction() {
+        nativeClearSyncTransaction(mNativeObject);
+    }
+
+    /**
      * Updates {@link SurfaceControl}, size, and format for a particular BLASTBufferQueue
      * @param sc The new SurfaceControl that this BLASTBufferQueue will update
      * @param width The new width for the buffer.
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
index 575b0ce..cc46a4b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -129,12 +129,9 @@
      * {@link WindowAreaComponent#STATUS_AVAILABLE} or
      * {@link WindowAreaComponent#STATUS_UNAVAILABLE} if the feature is supported or not in that
      * state respectively. When the rear display feature is triggered, the status is updated to be
-     * {@link WindowAreaComponent#STATUS_UNAVAILABLE}.
+     * {@link WindowAreaComponent#STATUS_ACTIVE}.
      * TODO(b/240727590): Prefix with AREA_
      *
-     * TODO(b/239833099): Add a STATUS_ACTIVE option to let apps know if a feature is currently
-     *  enabled.
-     *
      * @param consumer {@link Consumer} interested in receiving updates to the status of
      * rear display mode.
      */
@@ -407,18 +404,21 @@
         }
     }
 
-
     @GuardedBy("mLock")
     private int getCurrentRearDisplayModeStatus() {
         if (mRearDisplayState == INVALID_DEVICE_STATE) {
             return WindowAreaComponent.STATUS_UNSUPPORTED;
         }
 
-        if (mRearDisplaySessionStatus == WindowAreaComponent.SESSION_STATE_ACTIVE
-                || !ArrayUtils.contains(mCurrentSupportedDeviceStates, mRearDisplayState)
-                || isRearDisplayActive()) {
+        if (!ArrayUtils.contains(mCurrentSupportedDeviceStates, mRearDisplayState)) {
             return WindowAreaComponent.STATUS_UNAVAILABLE;
         }
+
+        if (mRearDisplaySessionStatus == WindowAreaComponent.SESSION_STATE_ACTIVE
+                || isRearDisplayActive()) {
+            return WindowAreaComponent.STATUS_ACTIVE;
+        }
+
         return WindowAreaComponent.STATUS_AVAILABLE;
     }
 
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/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 8c98c77..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
@@ -300,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;
         }
     }
@@ -401,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;
         }
@@ -548,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
@@ -663,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 5670fe6..d04ce15 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
@@ -249,7 +249,7 @@
         }, null);
     }
 
-    private boolean shouldSyncPipTransactionWithMenu() {
+    protected boolean shouldSyncPipTransactionWithMenu() {
         return mPipMenuController.isMenuVisible();
     }
 
@@ -277,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;
@@ -381,6 +381,10 @@
         return mPipTransitionController;
     }
 
+    PipAnimationController.PipTransactionHandler getPipTransactionHandler() {
+        return mPipTransactionHandler;
+    }
+
     public Rect getCurrentOrAnimatingBounds() {
         PipAnimationController.PipTransitionAnimator animator =
                 mPipAnimationController.getCurrentAnimator();
@@ -712,15 +716,16 @@
             return;
         }
 
+        final int animationType = shouldAlwaysFadeIn()
+                ? ANIM_TYPE_ALPHA
+                : mPipAnimationController.takeOneShotEnterAnimationType();
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            mPipTransitionController.setEnterAnimationType(animationType);
             // For Shell transition, we will animate the window in PipTransition#startAnimation
             // instead of #onTaskAppeared.
             return;
         }
 
-        final int animationType = shouldAlwaysFadeIn()
-                ? ANIM_TYPE_ALPHA
-                : mPipAnimationController.takeOneShotEnterAnimationType();
         if (mWaitForFixedRotation) {
             onTaskAppearedWithFixedRotation(animationType);
             return;
@@ -1385,7 +1390,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();
         }
@@ -1551,7 +1556,8 @@
         if (!isInPip()) {
             return;
         }
-        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/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index b743140..aa3afc5 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
@@ -274,9 +274,6 @@
         if (!requestHasPipEnter(request)) {
             throw new IllegalStateException("Called PiP augmentRequest when request has no PiP");
         }
-        mEnterAnimationType = mPipOrganizer.shouldAlwaysFadeIn()
-                ? ANIM_TYPE_ALPHA
-                : mPipAnimationController.takeOneShotEnterAnimationType();
         if (mEnterAnimationType == ANIM_TYPE_ALPHA) {
             mRequestedEnterTransition = transition;
             mRequestedEnterTask = request.getTriggerTask().token;
@@ -665,6 +662,11 @@
         return false;
     }
 
+    @Override
+    public void setEnterAnimationType(@PipAnimationController.AnimationType int type) {
+        mEnterAnimationType = type;
+    }
+
     private void startEnterAnimation(@NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction,
@@ -786,8 +788,6 @@
 
         final int enterAnimationType = mEnterAnimationType;
         if (enterAnimationType == ANIM_TYPE_ALPHA) {
-            // Restore to default type.
-            mEnterAnimationType = ANIM_TYPE_BOUNDS;
             startTransaction.setAlpha(leash, 0f);
         }
         startTransaction.apply();
@@ -823,6 +823,7 @@
             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) {
@@ -949,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 7979ce7..ff7ab8b 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
@@ -225,6 +225,10 @@
         throw new IllegalStateException("Request isn't entering PiP");
     }
 
+    /** Sets the type of animation when a PiP task appears. */
+    public void setEnterAnimationType(@PipAnimationController.AnimationType int type) {
+    }
+
     /** Play a transition animation for entering PiP on a specific PiP change. */
     public void startEnterAnimation(@NonNull final TransitionInfo.Change pipChange,
             @NonNull final SurfaceControl.Transaction startTransaction,
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/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/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 2c08cd4..51b8000 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
@@ -38,7 +38,6 @@
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.view.SurfaceControl;
@@ -139,20 +138,13 @@
                     t.setAlpha(parentChange.getLeash(), 1.f);
                     // and then animate this layer outside the parent (since, for example, this is
                     // the home task animating from fullscreen to part-screen).
-                    t.reparent(leash, info.getRoot(rootIdx).getLeash());
-                    t.setLayer(leash, info.getChanges().size() - i);
+                    t.reparent(parentChange.getLeash(), info.getRoot(rootIdx).getLeash());
+                    t.setLayer(parentChange.getLeash(), info.getChanges().size() - i);
                     // build the finish reparent/reposition
                     mFinishTransaction.reparent(leash, parentChange.getLeash());
                     mFinishTransaction.setPosition(leash,
                             change.getEndRelOffset().x, change.getEndRelOffset().y);
                 }
-                // TODO(shell-transitions): screenshot here
-                final Rect startBounds = new Rect(change.getStartAbsBounds());
-                final Rect endBounds = new Rect(change.getEndAbsBounds());
-                final Point rootOffset = info.getRoot(rootIdx).getOffset();
-                startBounds.offset(-rootOffset.x, -rootOffset.y);
-                endBounds.offset(-rootOffset.x, -rootOffset.y);
-                startExampleResizeAnimation(leash, startBounds, endBounds);
             }
             boolean isRootOrSplitSideRoot = change.getParent() == null
                     || topRoot.equals(change.getParent());
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 2ee3ca2..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
@@ -2414,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
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/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/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/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index e8d3b1f..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
@@ -462,7 +463,8 @@
                 createCredentialRequest.type,
                 createCredentialRequest.credentialData,
                 createCredentialRequest.candidateQueryData,
-                createCredentialRequest.isSystemProviderRequired
+                createCredentialRequest.isSystemProviderRequired,
+                createCredentialRequest.origin,
             )
             val appPreferredDefaultProviderId: String? =
                 if (!requestInfo.hasPermissionToOverrideDefault()) null
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/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt
index 4a6e0b6..0a7ccc5 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt
@@ -253,8 +253,8 @@
     @Synchronized
     fun unfreeze() {
         if (frozen) {
-            log(TAG, LogLevel.DEBUG, { str1 = name }, { "$str1 unfrozen" })
             frozen = false
+            log(TAG, LogLevel.DEBUG, { str1 = name }, { "$str1 unfrozen" })
         }
     }
 
diff --git a/packages/SystemUI/res/layout/controls_more_item.xml b/packages/SystemUI/res/layout/controls_more_item.xml
index da9c43c..73d1c54 100644
--- a/packages/SystemUI/res/layout/controls_more_item.xml
+++ b/packages/SystemUI/res/layout/controls_more_item.xml
@@ -13,13 +13,17 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<TextView
-    xmlns:android="http://schemas.android.com/apk/res/android"
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/controls_more_item_text"
     style="@style/Control.MenuItem"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:layout_gravity="start"
+    android:layout_height="@dimen/control_menu_item_height"
+    android:layout_gravity="center_vertical"
+    android:background="@drawable/controls_popup_item_background"
     android:paddingStart="@dimen/control_menu_horizontal_padding"
     android:paddingEnd="@dimen/control_menu_horizontal_padding"
-    android:textDirection="locale"/>
-
+    android:textDirection="locale"
+    android:textSize="@dimen/control_item_text_size"
+    tools:fontFamily="@null"
+    tools:text="@tools:sample/lorem/random" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/controls_spinner_item.xml b/packages/SystemUI/res/layout/controls_spinner_item.xml
index 4048d03..8119651 100644
--- a/packages/SystemUI/res/layout/controls_spinner_item.xml
+++ b/packages/SystemUI/res/layout/controls_spinner_item.xml
@@ -16,18 +16,18 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
-    android:layout_height="@dimen/control_popup_item_height"
+    android:layout_height="@dimen/control_apps_popup_item_height"
     android:background="@drawable/controls_popup_item_background"
     android:gravity="center_vertical|start"
     android:orientation="horizontal"
-    android:paddingStart="@dimen/control_popup_item_padding"
-    android:paddingEnd="@dimen/control_popup_item_padding">
+    android:paddingStart="@dimen/control_menu_horizontal_padding"
+    android:paddingEnd="@dimen/control_menu_horizontal_padding">
 
   <ImageView
       android:id="@+id/app_icon"
       android:layout_width="@dimen/controls_header_app_icon_size"
       android:layout_height="@dimen/controls_header_app_icon_size"
-      android:layout_marginEnd="@dimen/control_popup_item_padding"
+      android:layout_marginEnd="@dimen/control_menu_horizontal_padding"
       android:contentDescription="@null"
       tools:src="@drawable/ic_android" />
 
diff --git a/packages/SystemUI/res/layout/notification_snooze.xml b/packages/SystemUI/res/layout/notification_snooze.xml
index 11ec025..8b53680 100644
--- a/packages/SystemUI/res/layout/notification_snooze.xml
+++ b/packages/SystemUI/res/layout/notification_snooze.xml
@@ -21,6 +21,8 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="vertical"
+    android:paddingTop="2dp"
+    android:paddingBottom="2dp"
     android:background="?androidprv:attr/colorSurface"
     android:theme="@style/Theme.SystemUI">
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 0eb0a07..9cb8aa0 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1199,19 +1199,17 @@
     <dimen name="controls_top_margin">48dp</dimen>
     <dimen name="controls_content_margin_horizontal">0dp</dimen>
     <dimen name="control_header_text_size">24sp</dimen>
-    <dimen name="control_item_text_size">16sp</dimen>
+    <dimen name="control_item_text_size">14sp</dimen>
     <dimen name="control_menu_item_text_size">16sp</dimen>
-    <dimen name="control_menu_item_min_height">56dp</dimen>
+    <dimen name="control_menu_item_height">54dp</dimen>
     <dimen name="control_menu_vertical_padding">12dp</dimen>
-    <dimen name="control_menu_horizontal_padding">16dp</dimen>
-    <dimen name="control_popup_item_corner_radius">4dp</dimen>
-    <dimen name="control_popup_item_height">56dp</dimen>
-    <dimen name="control_popup_item_padding">16dp</dimen>
-    <dimen name="control_popup_items_divider_height">1dp</dimen>
+    <dimen name="control_menu_horizontal_padding">@dimen/notification_side_paddings</dimen>
+    <dimen name="control_apps_popup_item_height">56dp</dimen>
+    <dimen name="control_popup_item_corner_radius">@dimen/notification_corner_radius_small</dimen>
+    <dimen name="control_popup_items_divider_height">@dimen/controls_app_divider_height</dimen>
     <dimen name="control_popup_max_width">380dp</dimen>
-    <dimen name="control_popup_corner_radius">28dp</dimen>
+    <dimen name="control_popup_corner_radius">@dimen/notification_corner_radius</dimen>
     <dimen name="control_popup_horizontal_margin">16dp</dimen>
-    <dimen name="control_spinner_padding_vertical">24dp</dimen>
     <dimen name="control_spinner_padding_horizontal">20dp</dimen>
     <dimen name="control_text_size">14sp</dimen>
     <dimen name="control_icon_size">24dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 74ae954..19deefb 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3086,4 +3086,7 @@
 
     <!-- Content description for when assistant attention is active [CHAR LIMIT=NONE] -->
     <string name="assistant_attention_content_description">Assistant attention on</string>
+
+    <!--- Content of toast triggered when the notes app entry point is triggered without setting a default notes app. [CHAR LIMIT=NONE] -->
+    <string name="set_default_notes_app_toast_content">Set default notes app in Settings</string>
 </resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index a359d3b..9d0cc11 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -895,7 +895,7 @@
         <item name="android:textColor">@color/control_primary_text</item>
         <item name="android:singleLine">true</item>
         <item name="android:gravity">center_vertical</item>
-        <item name="android:minHeight">@dimen/control_menu_item_min_height</item>
+        <item name="android:minHeight">@dimen/control_menu_item_height</item>
     </style>
 
     <style name="Control.Spinner">
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 2dd2d39..c48aaf4 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -156,6 +156,7 @@
 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.constants.TrustAgentUiEvent;
 import com.android.systemui.keyguard.shared.model.AcquiredAuthenticationStatus;
 import com.android.systemui.keyguard.shared.model.AuthenticationStatus;
 import com.android.systemui.keyguard.shared.model.DetectionStatus;
@@ -537,6 +538,14 @@
 
             mLogger.logTrustGrantedWithFlags(flags, newlyUnlocked, userId, message);
             if (userId == getCurrentUser()) {
+                if (newlyUnlocked) {
+                    // if this callback is ever removed, this should then be logged in
+                    // TrustRepository
+                    mUiEventLogger.log(
+                            TrustAgentUiEvent.TRUST_AGENT_NEWLY_UNLOCKED,
+                            getKeyguardSessionId()
+                    );
+                }
                 final TrustGrantFlags trustGrantFlags = new TrustGrantFlags(flags);
                 for (int i = 0; i < mCallbacks.size(); i++) {
                     KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -4373,7 +4382,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/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 6c62a39..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)
         }
@@ -40,7 +41,7 @@
         mainExecutor.execute {
             action?.let {
                 if (event.tracking || (event.expanded && event.fraction > 0)) {
-                    Log.v(TAG, "Detected panel interaction, event: $event")
+                    Log.i(TAG, "Detected panel interaction, event: $event")
                     it.onPanelInteraction.run()
                     disable()
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java
index 25b1e3a..83e61d6 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java
@@ -155,8 +155,7 @@
     }
 
     @Override
-    public void onStart() {
-        super.onStart();
+    public void start() {
         registerBroadcastCallBack(mExecutor, mBroadcastCallback);
     }
 
@@ -200,8 +199,7 @@
     }
 
     @Override
-    public void onStop() {
-        super.onStop();
+    public void stop() {
         unregisterBroadcastCallBack(mBroadcastCallback);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialog.kt b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialog.kt
index 9e15c7e..f17d0f3 100644
--- a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialog.kt
@@ -86,13 +86,11 @@
         highlightContrast(toContrastLevel(initialContrast))
     }
 
-    override fun onStart() {
-        super.onStart()
+    override fun start() {
         uiModeManager.addContrastChangeListener(mainExecutor, this)
     }
 
-    override fun onStop() {
-        super.onStop()
+    override fun stop() {
         uiModeManager.removeContrastChangeListener(this)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsPopupMenu.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsPopupMenu.kt
index d08bc48..f7c8e96 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsPopupMenu.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsPopupMenu.kt
@@ -20,11 +20,18 @@
 import android.content.res.Resources
 import android.graphics.drawable.ColorDrawable
 import android.graphics.drawable.Drawable
-import android.view.Gravity
+import android.view.Gravity.END
+import android.view.Gravity.GravityFlags
+import android.view.Gravity.NO_GRAVITY
+import android.view.Gravity.START
 import android.view.View
+import android.view.View.MeasureSpec
+import android.view.ViewGroup
 import android.widget.ListPopupWindow
+import android.widget.ListView
 import android.widget.PopupWindow
 import com.android.systemui.R
+import kotlin.math.max
 
 class ControlsPopupMenu(context: Context) : ListPopupWindow(context) {
 
@@ -40,13 +47,13 @@
     private val dimDrawable: Drawable = ColorDrawable(resources.getColor(R.color.control_popup_dim))
 
     private var dismissListener: PopupWindow.OnDismissListener? = null
+    @GravityFlags private var dropDownGravity: Int = NO_GRAVITY
 
     init {
         setBackgroundDrawable(dialogBackground)
 
         inputMethodMode = INPUT_METHOD_NOT_NEEDED
         isModal = true
-        setDropDownGravity(Gravity.START)
 
         // dismiss method isn't called when popup is hidden by outside touch. So we need to
         // override a listener to remove a dimming foreground
@@ -59,30 +66,68 @@
     override fun show() {
         // need to call show() first in order to construct the listView
         super.show()
-
-        val paddedWidth = resources.displayMetrics.widthPixels - 2 * horizontalMargin
-        width = maxWidth.coerceAtMost(paddedWidth)
+        updateWidth()
         anchorView?.let {
-            horizontalOffset = -width / 2 + it.width / 2
-            verticalOffset = -it.height / 2
-            if (it.layoutDirection == View.LAYOUT_DIRECTION_RTL) {
-                horizontalOffset = -horizontalOffset
-            }
-
+            positionPopup(it)
             it.rootView.foreground = dimDrawable
         }
-
         with(listView!!) {
             clipToOutline = true
             background = dialogBackground
             dividerHeight = listDividerHeight
         }
-
         // actual show takes into account updated ListView specs
         super.show()
     }
 
+    override fun setDropDownGravity(@GravityFlags gravity: Int) {
+        super.setDropDownGravity(gravity)
+        dropDownGravity = gravity
+    }
+
     override fun setOnDismissListener(listener: PopupWindow.OnDismissListener?) {
         dismissListener = listener
     }
+
+    private fun updateWidth() {
+        val paddedWidth = resources.displayMetrics.widthPixels - 2 * horizontalMargin
+        val maxWidth = maxWidth.coerceAtMost(paddedWidth)
+        when (width) {
+            ViewGroup.LayoutParams.MATCH_PARENT -> {
+                width = maxWidth
+            }
+            ViewGroup.LayoutParams.WRAP_CONTENT -> {
+                width = listView!!.measureDesiredWidth(maxWidth).coerceAtMost(maxWidth)
+            }
+        }
+    }
+
+    private fun positionPopup(anchorView: View) {
+        when (dropDownGravity) {
+            NO_GRAVITY -> {
+                horizontalOffset = (-width + anchorView.width) / 2
+                if (anchorView.layoutDirection == View.LAYOUT_DIRECTION_RTL) {
+                    horizontalOffset = -horizontalOffset
+                }
+            }
+            END,
+            START -> {
+                horizontalOffset = 0
+            }
+        }
+        verticalOffset = -anchorView.height / 2
+    }
+
+    private fun ListView.measureDesiredWidth(maxWidth: Int): Int {
+        var maxItemWidth = 0
+        repeat(adapter.count) {
+            val view = adapter.getView(it, null, listView)
+            view.measure(
+                MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST),
+                MeasureSpec.UNSPECIFIED
+            )
+            maxItemWidth = max(maxItemWidth, view.measuredWidth)
+        }
+        return maxItemWidth
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index d4ce9b6..92607c6 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -34,6 +34,7 @@
 import android.service.controls.ControlsProviderService
 import android.util.Log
 import android.view.ContextThemeWrapper
+import android.view.Gravity
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
@@ -72,7 +73,6 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.globalactions.GlobalActionsPopupMenu
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -540,12 +540,12 @@
         val anchor = parent.requireViewById<ImageView>(R.id.controls_more)
         anchor.setOnClickListener(object : View.OnClickListener {
             override fun onClick(v: View) {
-                popup = GlobalActionsPopupMenu(
-                        popupThemedContext,
-                        false /* isDropDownMode */
-                ).apply {
-                    setAnchorView(anchor)
+                popup = ControlsPopupMenu(popupThemedContext).apply {
+                    width = ViewGroup.LayoutParams.WRAP_CONTENT
+                    anchorView = anchor
+                    setDropDownGravity(Gravity.END)
                     setAdapter(adapter)
+
                     setOnItemClickListener(object : AdapterView.OnItemClickListener {
                         override fun onItemClick(
                             parent: AdapterView<*>,
@@ -618,7 +618,8 @@
         anchor.setOnClickListener(object : View.OnClickListener {
             override fun onClick(v: View) {
                 popup = ControlsPopupMenu(popupThemedContext).apply {
-                    setAnchorView(anchor)
+                    anchorView = anchor
+                    width = ViewGroup.LayoutParams.MATCH_PARENT
                     setAdapter(adapter)
 
                     setOnItemClickListener(object : AdapterView.OnItemClickListener {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 9bf6b2a..20d690e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -48,6 +48,7 @@
 import com.android.systemui.shortcut.ShortcutKeyDispatcher
 import com.android.systemui.statusbar.notification.InstantAppNotifier
 import com.android.systemui.statusbar.phone.KeyguardLiftController
+import com.android.systemui.statusbar.phone.LetterboxModule
 import com.android.systemui.stylus.StylusUsiPowerStartable
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
 import com.android.systemui.theme.ThemeOverlayController
@@ -66,7 +67,8 @@
  */
 @Module(includes = [
     MultiUserUtilsModule::class,
-    StartControlsStartableModule::class
+    StartControlsStartableModule::class,
+    LetterboxModule::class,
 ])
 abstract class SystemUICoreStartableModule {
     /** Inject into AuthController.  */
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
index 276a290..7d1ffca 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@@ -39,6 +39,11 @@
     private val dumpables: MutableMap<String, RegisteredDumpable<Dumpable>> = ArrayMap()
     private val buffers: MutableMap<String, RegisteredDumpable<LogBuffer>> = ArrayMap()
 
+    /** See [registerCriticalDumpable]. */
+    fun registerCriticalDumpable(module: Dumpable) {
+        registerCriticalDumpable(module::class.java.simpleName, module)
+    }
+
     /**
      * Registers a dumpable to be called during the CRITICAL section of the bug report.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index f01749a..dfe8697 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -67,10 +67,6 @@
     // TODO(b/254512538): Tracking Bug
     val INSTANT_VOICE_REPLY = unreleasedFlag(111, "instant_voice_reply")
 
-    // TODO(b/254512425): Tracking Bug
-    val NOTIFICATION_MEMORY_MONITOR_ENABLED =
-        releasedFlag(112, "notification_memory_monitor_enabled")
-
     /**
      * This flag is server-controlled and should stay as [unreleasedFlag] since we never want to
      * enable it on release builds.
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 07753ca..4be47ec 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -2477,8 +2477,7 @@
         }
 
         @Override
-        protected void onStart() {
-            super.onStart();
+        protected void start() {
             mGlobalActionsLayout.updateList();
 
             if (mBackgroundDrawable instanceof ScrimDrawable) {
@@ -2509,8 +2508,7 @@
         }
 
         @Override
-        protected void onStop() {
-            super.onStop();
+        protected void stop() {
             mColorExtractor.removeOnColorsChangedListener(this);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt b/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
index 801b165..c41b5e4 100644
--- a/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.
+ */
+
 package com.android.systemui.graphics
 
 import android.annotation.AnyThread
@@ -20,6 +36,7 @@
 import android.util.Size
 import androidx.core.content.res.ResourcesCompat
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import java.io.IOException
 import javax.inject.Inject
@@ -35,7 +52,7 @@
 class ImageLoader
 @Inject
 constructor(
-    private val defaultContext: Context,
+    @Application private val defaultContext: Context,
     @Background private val backgroundDispatcher: CoroutineDispatcher
 ) {
 
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 c4fc883..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
@@ -133,6 +134,7 @@
     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
@@ -211,6 +213,13 @@
         observeFaceAuthGatingChecks()
         observeFaceDetectGatingChecks()
         observeFaceAuthResettingConditions()
+        listenForSchedulingWatchdog()
+    }
+
+    private fun listenForSchedulingWatchdog() {
+        keyguardTransitionInteractor.anyStateToGoneTransition
+            .onEach { faceManager?.scheduleWatchdog() }
+            .launchIn(applicationScope)
     }
 
     private fun observeFaceAuthResettingConditions() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/constants/TrustAgentUiEvent.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/constants/TrustAgentUiEvent.kt
new file mode 100644
index 0000000..ef6079f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/constants/TrustAgentUiEvent.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.shared.constants
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+
+enum class TrustAgentUiEvent(private val metricId: Int) : UiEventLogger.UiEventEnum {
+    @UiEvent(doc = "TrustAgent newly unlocked the device") TRUST_AGENT_NEWLY_UNLOCKED(1361);
+    override fun getId() = metricId
+}
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/pipeline/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt
index a1d9214..ed4eef9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.media.controls.pipeline
 
 import android.media.session.MediaController
+import android.media.session.MediaSession
 import android.media.session.PlaybackState
 import android.os.SystemProperties
 import com.android.internal.annotations.VisibleForTesting
@@ -148,7 +149,7 @@
         reusedListener?.let {
             val wasPlaying = it.isPlaying()
             logger.logUpdateListener(key, wasPlaying)
-            it.mediaData = data
+            it.setMediaData(data)
             it.key = key
             mediaListeners[key] = it
             if (wasPlaying != it.isPlaying()) {
@@ -208,24 +209,7 @@
         var resumption: Boolean? = null
         var destroyed = false
         var expiration = Long.MAX_VALUE
-
-        var mediaData: MediaData = data
-            set(value) {
-                destroyed = false
-                mediaController?.unregisterCallback(this)
-                field = value
-                val token = field.token
-                mediaController =
-                    if (token != null) {
-                        mediaControllerFactory.create(token)
-                    } else {
-                        null
-                    }
-                mediaController?.registerCallback(this)
-                // Let's register the cancellations, but not dispatch events now.
-                // Timeouts didn't happen yet and reentrant events are troublesome.
-                processState(mediaController?.playbackState, dispatchEvents = false)
-            }
+        var sessionToken: MediaSession.Token? = null
 
         // Resume controls may have null token
         private var mediaController: MediaController? = null
@@ -236,7 +220,7 @@
         fun isPlaying() = lastState?.state?.isPlaying() ?: false
 
         init {
-            mediaData = data
+            setMediaData(data)
         }
 
         fun destroy() {
@@ -245,8 +229,28 @@
             destroyed = true
         }
 
+        fun setMediaData(data: MediaData) {
+            sessionToken = data.token
+            destroyed = false
+            mediaController?.unregisterCallback(this)
+            mediaController =
+                if (data.token != null) {
+                    mediaControllerFactory.create(data.token)
+                } else {
+                    null
+                }
+            mediaController?.registerCallback(this)
+            // Let's register the cancellations, but not dispatch events now.
+            // Timeouts didn't happen yet and reentrant events are troublesome.
+            processState(
+                mediaController?.playbackState,
+                dispatchEvents = false,
+                currentResumption = data.resumption,
+            )
+        }
+
         override fun onPlaybackStateChanged(state: PlaybackState?) {
-            processState(state, dispatchEvents = true)
+            processState(state, dispatchEvents = true, currentResumption = resumption)
         }
 
         override fun onSessionDestroyed() {
@@ -263,14 +267,18 @@
             }
         }
 
-        private fun processState(state: PlaybackState?, dispatchEvents: Boolean) {
+        private fun processState(
+            state: PlaybackState?,
+            dispatchEvents: Boolean,
+            currentResumption: Boolean?,
+        ) {
             logger.logPlaybackState(key, state)
 
             val playingStateSame = (state?.state?.isPlaying() == isPlaying())
             val actionsSame =
                 (lastState?.actions == state?.actions) &&
                     areCustomActionListsEqual(lastState?.customActions, state?.customActions)
-            val resumptionChanged = resumption != mediaData.resumption
+            val resumptionChanged = resumption != currentResumption
 
             lastState = state
 
@@ -282,7 +290,7 @@
             if (playingStateSame && !resumptionChanged) {
                 return
             }
-            resumption = mediaData.resumption
+            resumption = currentResumption
 
             val playing = isPlaying()
             if (!playing) {
@@ -294,7 +302,7 @@
                 }
                 expireMediaTimeout(key, "PLAYBACK STATE CHANGED - $state, $resumption")
                 val timeout =
-                    if (mediaData.resumption) {
+                    if (currentResumption == true) {
                         RESUME_MEDIA_TIMEOUT
                     } else {
                         PAUSED_MEDIA_TIMEOUT
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/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 9606bcf..08e47a0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -273,8 +273,7 @@
     }
 
     @Override
-    public void onStart() {
-        super.onStart();
+    public void start() {
         mMediaOutputController.start(this);
         if (isBroadcastSupported() && !mIsLeBroadcastCallbackRegistered) {
             mMediaOutputController.registerLeBroadcastServiceCallback(mExecutor,
@@ -284,8 +283,7 @@
     }
 
     @Override
-    public void onStop() {
-        super.onStop();
+    public void stop() {
         if (isBroadcastSupported() && mIsLeBroadcastCallbackRegistered) {
             mMediaOutputController.unregisterLeBroadcastServiceCallback(mBroadcastCallback);
             mIsLeBroadcastCallbackRegistered = false;
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
index f0ff140..abf0932 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
@@ -212,8 +212,8 @@
     }
 
     @Override
-    public void onStart() {
-        super.onStart();
+    public void start() {
+        super.start();
         if (!mIsLeBroadcastAssistantCallbackRegistered) {
             mIsLeBroadcastAssistantCallbackRegistered = true;
             mMediaOutputController.registerLeBroadcastAssistantServiceCallback(mExecutor,
@@ -223,8 +223,8 @@
     }
 
     @Override
-    public void onStop() {
-        super.onStop();
+    public void stop() {
+        super.stop();
         if (mIsLeBroadcastAssistantCallbackRegistered) {
             mIsLeBroadcastAssistantCallbackRegistered = false;
             mMediaOutputController.unregisterLeBroadcastAssistantServiceCallback(
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index 334c70b..5f4e7cac 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -34,7 +34,9 @@
 import android.os.UserHandle
 import android.os.UserManager
 import android.util.Log
+import android.widget.Toast
 import androidx.annotation.VisibleForTesting
+import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.devicepolicy.areKeyguardShortcutsDisabled
 import com.android.systemui.notetask.NoteTaskRoleManagerExt.createNoteShortcutInfoAsUser
@@ -170,7 +172,13 @@
             return
         }
 
-        val info = resolver.resolveInfo(entryPoint, isKeyguardLocked) ?: return
+        val info = resolver.resolveInfo(entryPoint, isKeyguardLocked)
+
+        if (info == null) {
+            logDebug { "Default notes app isn't set" }
+            showNoDefaultNotesAppToast()
+            return
+        }
 
         infoReference.set(info)
 
@@ -207,6 +215,12 @@
         logDebug { "onShowNoteTask - completed: $info" }
     }
 
+    @VisibleForTesting
+    fun showNoDefaultNotesAppToast() {
+        Toast.makeText(context, R.string.set_default_notes_app_toast_content, Toast.LENGTH_SHORT)
+            .show()
+    }
+
     /**
      * Set `android:enabled` property in the `AndroidManifest` associated with the Shortcut
      * component to [value].
@@ -214,7 +228,7 @@
      * If the shortcut entry `android:enabled` is set to `true`, the shortcut will be visible in the
      * Widget Picker to all users.
      */
-    fun setNoteTaskShortcutEnabled(value: Boolean) {
+    fun setNoteTaskShortcutEnabled(value: Boolean, user: UserHandle) {
         val componentName = ComponentName(context, CreateNoteTaskShortcutActivity::class.java)
 
         val enabledState =
@@ -224,7 +238,16 @@
                 PackageManager.COMPONENT_ENABLED_STATE_DISABLED
             }
 
-        context.packageManager.setComponentEnabledSetting(
+        // If the required user matches the tracking user, the injected context is already a context
+        // of the required user. Avoid calling #createContextAsUser because creating a context for
+        // a user takes time.
+        val userContext =
+            if (user == userTracker.userHandle) {
+                context
+            } else {
+                context.createContextAsUser(user, /* flags= */ 0)
+            }
+        userContext.packageManager.setComponentEnabledSetting(
             componentName,
             enabledState,
             PackageManager.DONT_KILL_APP,
@@ -246,7 +269,7 @@
         val packageName = roleManager.getDefaultRoleHolderAsUser(ROLE_NOTES, user)
         val hasNotesRoleHolder = isEnabled && !packageName.isNullOrEmpty()
 
-        setNoteTaskShortcutEnabled(hasNotesRoleHolder)
+        setNoteTaskShortcutEnabled(hasNotesRoleHolder, user)
 
         if (hasNotesRoleHolder) {
             shortcutManager.enableShortcuts(listOf(SHORTCUT_ID))
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
index 23ee13b..7bb615b 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -20,6 +20,7 @@
 import android.view.KeyEvent
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.CommandQueue
 import com.android.wm.shell.bubbles.Bubbles
 import java.util.Optional
@@ -36,6 +37,7 @@
     private val optionalBubbles: Optional<Bubbles>,
     @Background private val backgroundExecutor: Executor,
     @NoteTaskEnabledKey private val isEnabled: Boolean,
+    private val userTracker: UserTracker,
 ) {
 
     @VisibleForTesting
@@ -44,8 +46,9 @@
             override fun handleSystemKey(key: KeyEvent) {
                 if (key.keyCode == KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL) {
                     controller.showNoteTask(NoteTaskEntryPoint.TAIL_BUTTON)
-                } else if (key.keyCode == KeyEvent.KEYCODE_N && key.isMetaPressed &&
-                        key.isCtrlPressed) {
+                } else if (
+                    key.keyCode == KeyEvent.KEYCODE_N && key.isMetaPressed && key.isCtrlPressed
+                ) {
                     controller.showNoteTask(NoteTaskEntryPoint.KEYBOARD_SHORTCUT)
                 }
             }
@@ -55,7 +58,7 @@
         // Guard against feature not being enabled or mandatory dependencies aren't available.
         if (!isEnabled || optionalBubbles.isEmpty) return
 
-        controller.setNoteTaskShortcutEnabled(true)
+        controller.setNoteTaskShortcutEnabled(true, userTracker.userHandle)
         commandQueue.addCallback(callbacks)
         roleManager.addOnRoleHoldersChangedListenerAsUser(
             backgroundExecutor,
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
index 03145a7..35a7cf1 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
@@ -90,8 +90,7 @@
         }
     }
 
-    override fun onStop() {
-        super.onStop()
+    override fun stop() {
         dismissed.set(true)
         val iterator = dismissListeners.iterator()
         while (iterator.hasNext()) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index 039dafb..380b85c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -259,8 +259,7 @@
     }
 
     @Override
-    public void onStart() {
-        super.onStart();
+    public void start() {
         if (DEBUG) {
             Log.d(TAG, "onStart");
         }
@@ -280,8 +279,7 @@
     }
 
     @Override
-    public void onStop() {
-        super.onStop();
+    public void stop() {
         if (DEBUG) {
             Log.d(TAG, "onStop");
         }
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 222a0f4f..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);
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index b4653be..f0815e9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -19,11 +19,14 @@
 import android.animation.Animator
 import android.animation.AnimatorListenerAdapter
 import android.annotation.IdRes
+import android.app.PendingIntent
 import android.app.StatusBarManager
+import android.content.Intent
 import android.content.res.Configuration
 import android.os.Bundle
 import android.os.Trace
 import android.os.Trace.TRACE_TAG_APP
+import android.provider.AlarmClock
 import android.util.Pair
 import android.view.DisplayCutout
 import android.view.View
@@ -41,6 +44,7 @@
 import com.android.systemui.demomode.DemoMode
 import com.android.systemui.demomode.DemoModeController
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.qs.ChipVisibilityListener
 import com.android.systemui.qs.HeaderPrivacyIconsController
 import com.android.systemui.shade.ShadeHeaderController.Companion.HEADER_TRANSITION_ID
@@ -58,6 +62,7 @@
 import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.SHADE_HEADER
 import com.android.systemui.statusbar.policy.Clock
 import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.NextAlarmController
 import com.android.systemui.statusbar.policy.VariableDateView
 import com.android.systemui.statusbar.policy.VariableDateViewController
 import com.android.systemui.util.ViewController
@@ -91,6 +96,8 @@
     private val combinedShadeHeadersConstraintManager: CombinedShadeHeadersConstraintManager,
     private val demoModeController: DemoModeController,
     private val qsBatteryModeController: QsBatteryModeController,
+    private val nextAlarmController: NextAlarmController,
+    private val activityStarter: ActivityStarter,
 ) : ViewController<View>(header), Dumpable {
 
     companion object {
@@ -103,6 +110,8 @@
         @VisibleForTesting
         internal val LARGE_SCREEN_HEADER_CONSTRAINT = R.id.large_screen_header_constraint
 
+        @VisibleForTesting internal val DEFAULT_CLOCK_INTENT = Intent(AlarmClock.ACTION_SHOW_ALARMS)
+
         private fun Int.stateToString() =
             when (this) {
                 QQS_HEADER_CONSTRAINT -> "QQS Header"
@@ -125,6 +134,7 @@
     private var roundedCorners = 0
     private var cutout: DisplayCutout? = null
     private var lastInsets: WindowInsets? = null
+    private var nextAlarmIntent: PendingIntent? = null
 
     private var qsDisabled = false
     private var visible = false
@@ -252,6 +262,11 @@
             }
         }
 
+    private val nextAlarmCallback =
+        NextAlarmController.NextAlarmChangeCallback { nextAlarm ->
+            nextAlarmIntent = nextAlarm?.showIntent
+        }
+
     override fun onInit() {
         variableDateViewControllerFactory.create(date as VariableDateView).init()
         batteryMeterViewController.init()
@@ -286,19 +301,23 @@
 
             mShadeCarrierGroup.setPaddingRelative((v.width * v.scaleX).toInt(), 0, 0, 0)
         }
+        clock.setOnClickListener { launchClockActivity() }
 
         dumpManager.registerDumpable(this)
         configurationController.addCallback(configurationControllerListener)
         demoModeController.addCallback(demoModeReceiver)
         statusBarIconController.addIconGroup(iconManager)
+        nextAlarmController.addCallback(nextAlarmCallback)
     }
 
     override fun onViewDetached() {
+        clock.setOnClickListener(null)
         privacyIconsController.chipVisibilityListener = null
         dumpManager.unregisterDumpable(this::class.java.simpleName)
         configurationController.removeCallback(configurationControllerListener)
         demoModeController.removeCallback(demoModeReceiver)
         statusBarIconController.removeIconGroup(iconManager)
+        nextAlarmController.removeCallback(nextAlarmCallback)
     }
 
     fun disable(state1: Int, state2: Int, animate: Boolean) {
@@ -318,6 +337,15 @@
             .start()
     }
 
+    @VisibleForTesting
+    internal fun launchClockActivity() {
+        if (nextAlarmIntent != null) {
+            activityStarter.postStartActivityDismissingKeyguard(nextAlarmIntent)
+        } else {
+            activityStarter.postStartActivityDismissingKeyguard(DEFAULT_CLOCK_INTENT, 0 /*delay */)
+        }
+    }
+
     private fun loadConstraints() {
         // Use resources.getXml instead of passing the resource id due to bug b/205018300
         header
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index ced725e..ea9817c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -702,6 +702,13 @@
         mProcessArtworkTasks.remove(task);
     }
 
+    // TODO(b/273443374): remove
+    public boolean isLockscreenWallpaperOnNotificationShade() {
+        return mBackdrop != null && mLockscreenWallpaper != null
+                && !mLockscreenWallpaper.isLockscreenLiveWallpaperEnabled()
+                && (mBackdropFront.isVisibleToUser() || mBackdropBack.isVisibleToUser());
+    }
+
     /**
      * {@link AsyncTask} to prepare album art for use as backdrop on lock screen.
      */
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/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 8eef3f3..0ed4175 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -20,7 +20,6 @@
 import com.android.systemui.ForegroundServiceNotificationListener
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.people.widget.PeopleSpaceWidgetManager
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
 import com.android.systemui.statusbar.NotificationListener
@@ -117,9 +116,7 @@
         notificationLogger.setUpWithContainer(listContainer)
         peopleSpaceWidgetManager.attach(notificationListener)
         fgsNotifListener.init()
-        if (featureFlags.isEnabled(Flags.NOTIFICATION_MEMORY_MONITOR_ENABLED)) {
-            memoryMonitor.get().init()
-        }
+        memoryMonitor.get().init()
     }
 
     // TODO: Convert all functions below this line into listeners instead of public methods
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 7596ce0..0ec20ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -2614,6 +2614,7 @@
                 }
                 mRemoteInputManager.closeRemoteInputs();
                 if (mLockscreenUserManager.isCurrentProfile(getSendingUserId())) {
+                    mShadeLogger.d("ACTION_CLOSE_SYSTEM_DIALOGS intent: closing shade");
                     int flags = CommandQueue.FLAG_EXCLUDE_NONE;
                     if (reason != null) {
                         if (reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) {
@@ -2628,6 +2629,8 @@
                         }
                     }
                     mShadeController.animateCollapseShade(flags);
+                } else {
+                    mShadeLogger.d("ACTION_CLOSE_SYSTEM_DIALOGS intent: non-matching user ID");
                 }
             } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
                 if (mNotificationShadeWindowController != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 3268032..2814e8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -49,6 +49,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.disableflags.DisableStateTracker;
@@ -119,6 +120,9 @@
     private final Object mLock = new Object();
     private final KeyguardLogger mLogger;
 
+    // TODO(b/273443374): remove
+    private NotificationMediaManager mNotificationMediaManager;
+
     private final ConfigurationController.ConfigurationListener mConfigurationListener =
             new ConfigurationController.ConfigurationListener() {
                 @Override
@@ -283,7 +287,8 @@
             SecureSettings secureSettings,
             CommandQueue commandQueue,
             @Main Executor mainExecutor,
-            KeyguardLogger logger
+            KeyguardLogger logger,
+            NotificationMediaManager notificationMediaManager
     ) {
         super(view);
         mCarrierTextController = carrierTextController;
@@ -335,6 +340,7 @@
                 /* mask2= */ DISABLE2_SYSTEM_ICONS,
                 this::updateViewState
         );
+        mNotificationMediaManager = notificationMediaManager;
     }
 
     @Override
@@ -484,8 +490,11 @@
                     * (1.0f - mKeyguardHeadsUpShowingAmount);
         }
 
-        if (mSystemEventAnimator.isAnimationRunning()) {
+        if (mSystemEventAnimator.isAnimationRunning()
+                && !mNotificationMediaManager.isLockscreenWallpaperOnNotificationShade()) {
             newAlpha = Math.min(newAlpha, mSystemEventAnimatorAlpha);
+        } else {
+            mView.setTranslationX(0);
         }
 
         boolean hideForBypass =
@@ -625,11 +634,21 @@
 
     private StatusBarSystemEventDefaultAnimator getSystemEventAnimator(boolean isAnimationRunning) {
         return new StatusBarSystemEventDefaultAnimator(getResources(), (alpha) -> {
-            mSystemEventAnimatorAlpha = alpha;
+            // TODO(b/273443374): remove if-else condition
+            if (!mNotificationMediaManager.isLockscreenWallpaperOnNotificationShade()) {
+                mSystemEventAnimatorAlpha = alpha;
+            } else {
+                mSystemEventAnimatorAlpha = 1f;
+            }
             updateViewState();
             return Unit.INSTANCE;
         }, (translationX) -> {
-            mView.setTranslationX(translationX);
+            // TODO(b/273443374): remove if-else condition
+            if (!mNotificationMediaManager.isLockscreenWallpaperOnNotificationShade()) {
+                mView.setTranslationX(translationX);
+            } else {
+                mView.setTranslationX(0);
+            }
             return Unit.INSTANCE;
         }, isAnimationRunning);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
index 3989854..f742645 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
@@ -26,10 +26,10 @@
 import com.android.internal.statusbar.LetterboxDetails
 import com.android.internal.util.ContrastColorUtil
 import com.android.internal.view.AppearanceRegion
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
 import java.io.PrintWriter
 import java.util.Arrays
@@ -50,25 +50,21 @@
  * Responsible for calculating the [Appearance] and [AppearanceRegion] for the status bar when apps
  * are letterboxed.
  */
-@CentralSurfacesScope
+@SysUISingleton
 class LetterboxAppearanceCalculator
 @Inject
 constructor(
     private val lightBarController: LightBarController,
-    private val dumpManager: DumpManager,
+    dumpManager: DumpManager,
     private val letterboxBackgroundProvider: LetterboxBackgroundProvider,
-) : OnStatusBarViewInitializedListener, CentralSurfacesComponent.Startable {
+) : OnStatusBarViewInitializedListener, Dumpable {
+
+    init {
+        dumpManager.registerCriticalDumpable(this)
+    }
 
     private var statusBarBoundsProvider: StatusBarBoundsProvider? = null
 
-    override fun start() {
-        dumpManager.registerCriticalDumpable(javaClass.simpleName) { pw, _ -> dump(pw) }
-    }
-
-    override fun stop() {
-        dumpManager.unregisterDumpable(javaClass.simpleName)
-    }
-
     private var lastAppearance: Int? = null
     private var lastAppearanceRegions: Array<AppearanceRegion>? = null
     private var lastLetterboxes: Array<LetterboxDetails>? = null
@@ -216,8 +212,8 @@
         return this.intersect(other)
     }
 
-    private fun dump(printWriter: PrintWriter) {
-        printWriter.println(
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.println(
             """
            lastAppearance: ${lastAppearance?.toAppearanceString()}
            lastAppearanceRegion: ${Arrays.toString(lastAppearanceRegions)},
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt
index 2763750..34c7059e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt
@@ -22,28 +22,25 @@
 import android.os.Handler
 import android.os.RemoteException
 import android.view.IWindowManager
+import com.android.systemui.CoreStartable
 import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
 import java.io.PrintWriter
 import java.util.concurrent.Executor
 import javax.inject.Inject
 
 /** Responsible for providing information about the background of letterboxed apps. */
-@CentralSurfacesScope
+@SysUISingleton
 class LetterboxBackgroundProvider
 @Inject
 constructor(
     private val windowManager: IWindowManager,
     @Background private val backgroundExecutor: Executor,
-    private val dumpManager: DumpManager,
     private val wallpaperManager: WallpaperManager,
     @Main private val mainHandler: Handler,
-) : CentralSurfacesComponent.Startable, Dumpable {
-
+) : CoreStartable, Dumpable {
     @ColorInt
     var letterboxBackgroundColor: Int = Color.BLACK
         private set
@@ -57,7 +54,6 @@
         }
 
     override fun start() {
-        dumpManager.registerDumpable(javaClass.simpleName, this)
         fetchBackgroundColorInfo()
         wallpaperManager.addOnColorsChangedListener(wallpaperColorsListener, mainHandler)
     }
@@ -74,11 +70,6 @@
         }
     }
 
-    override fun stop() {
-        dumpManager.unregisterDumpable(javaClass.simpleName)
-        wallpaperManager.removeOnColorsChangedListener(wallpaperColorsListener)
-    }
-
     override fun dump(pw: PrintWriter, args: Array<out String>) {
         pw.println(
             """
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxModule.kt
new file mode 100644
index 0000000..2e3f0d0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxModule.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.statusbar.phone
+
+import com.android.systemui.CoreStartable
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+abstract class LetterboxModule {
+    @Binds
+    @IntoMap
+    @ClassKey(LetterboxBackgroundProvider::class)
+    abstract fun bindFeature(impl: LetterboxBackgroundProvider): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index 0814ea5..c16877a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -233,6 +233,11 @@
         }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
     }
 
+    // TODO(b/273443374): remove
+    public boolean isLockscreenLiveWallpaperEnabled() {
+        return mWallpaperManager.isLockscreenLiveWallpaperEnabled();
+    }
+
     @Override
     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println(getClass().getSimpleName() + ":");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 2027305..bb22365 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -191,7 +191,7 @@
     }
 
     @Override
-    protected void onStart() {
+    protected final void onStart() {
         super.onStart();
 
         if (mDismissReceiver != null) {
@@ -204,10 +204,18 @@
         mDialogManager.setShowing(this, true);
         mSysUiState.setFlag(QuickStepContract.SYSUI_STATE_DIALOG_SHOWING, true)
                 .commitUpdate(mContext.getDisplayId());
+
+        start();
     }
 
+    /**
+     * Called when {@link #onStart} is called. Subclasses wishing to override {@link #onStart()}
+     * should override this method instead.
+     */
+    protected void start() {}
+
     @Override
-    protected void onStop() {
+    protected final void onStop() {
         super.onStop();
 
         if (mDismissReceiver != null) {
@@ -218,8 +226,16 @@
         mDialogManager.setShowing(this, false);
         mSysUiState.setFlag(QuickStepContract.SYSUI_STATE_DIALOG_SHOWING, false)
                 .commitUpdate(mContext.getDisplayId());
+
+        stop();
     }
 
+    /**
+     * Called when {@link #onStop} is called. Subclasses wishing to override {@link #onStop()}
+     * should override this method instead.
+     */
+    protected void stop() {}
+
     public void setShowForAllUsers(boolean show) {
         setShowForAllUsers(this, show);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java
index b0532d7..f72e74b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java
@@ -16,17 +16,15 @@
 
 package com.android.systemui.statusbar.phone.dagger;
 
-import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator;
-import com.android.systemui.statusbar.phone.LetterboxBackgroundProvider;
 import com.android.systemui.statusbar.phone.SystemBarAttributesListener;
 
-import java.util.Set;
-
 import dagger.Binds;
 import dagger.Module;
 import dagger.multibindings.IntoSet;
 import dagger.multibindings.Multibinds;
 
+import java.util.Set;
+
 @Module
 interface CentralSurfacesStartableModule {
     @Multibinds
@@ -34,16 +32,6 @@
 
     @Binds
     @IntoSet
-    CentralSurfacesComponent.Startable letterboxAppearanceCalculator(
-            LetterboxAppearanceCalculator letterboxAppearanceCalculator);
-
-    @Binds
-    @IntoSet
     CentralSurfacesComponent.Startable sysBarAttrsListener(
             SystemBarAttributesListener systemBarAttributesListener);
-
-    @Binds
-    @IntoSet
-    CentralSurfacesComponent.Startable letterboxBgProvider(
-            LetterboxBackgroundProvider letterboxBackgroundProvider);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
index e3ed2b4..db7fa14 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
@@ -186,8 +186,7 @@
     }
 
     @Override
-    protected void onStart() {
-        super.onStart();
+    protected void start() {
         mShowTime = System.currentTimeMillis();
         synchronized (mTimerLock) {
             if (mNoUserActionRunnable != null) {
@@ -198,7 +197,7 @@
     }
 
     @Override
-    protected void onStop() {
+    protected void stop() {
         synchronized (mTimerLock) {
             if (mCancelScheduledNoUserActionRunnable != null) {
                 mCancelScheduledNoUserActionRunnable.run();
diff --git a/packages/SystemUI/src/com/android/systemui/volume/SafetyWarningDialog.java b/packages/SystemUI/src/com/android/systemui/volume/SafetyWarningDialog.java
index 5b188b2..d42b964 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/SafetyWarningDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/SafetyWarningDialog.java
@@ -95,8 +95,7 @@
     }
 
     @Override
-    protected void onStart() {
-        super.onStart();
+    protected void start() {
         mShowTime = System.currentTimeMillis();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialog.java
index 87a167b..96936e3 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialog.java
@@ -196,16 +196,14 @@
     }
 
     @Override
-    protected void onStart() {
-        super.onStart();
+    protected void start() {
         Log.d(TAG, "onStart");
         mLifecycleRegistry.setCurrentState(Lifecycle.State.STARTED);
         mLifecycleRegistry.setCurrentState(Lifecycle.State.RESUMED);
     }
 
     @Override
-    protected void onStop() {
-        super.onStop();
+    protected void stop() {
         Log.d(TAG, "onStop");
         mLifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED);
     }
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/controls/ui/ControlsPopupMenuTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsPopupMenuTest.kt
index 86e2bd3..df6fa11 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsPopupMenuTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsPopupMenuTest.kt
@@ -22,6 +22,7 @@
 import android.testing.AndroidTestingRunner
 import android.util.DisplayMetrics
 import android.view.View
+import android.view.ViewGroup
 import android.widget.PopupWindow.OnDismissListener
 import androidx.test.ext.junit.rules.ActivityScenarioRule
 import androidx.test.filters.SmallTest
@@ -29,13 +30,17 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.activity.EmptyTestActivity
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.widget.FakeListAdapter
+import com.android.systemui.widget.FakeListAdapter.FakeListAdapterItem
 import com.google.common.truth.Truth.assertThat
+import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -52,10 +57,16 @@
 
     @Rule @JvmField val activityScenarioRule = ActivityScenarioRule(EmptyTestActivity::class.java)
 
-    private val testDisplayMetrics: DisplayMetrics = DisplayMetrics()
+    private val testDisplayMetrics = DisplayMetrics()
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+    }
 
     @Test
-    fun testDismissListenerWorks() = testPopup { popupMenu ->
+    fun testDismissListenerWorks() = testPopup { activity, popupMenu ->
+        popupMenu.setAdapter(FakeListAdapter())
         val listener = mock(OnDismissListener::class.java)
         popupMenu.setOnDismissListener(listener)
         popupMenu.show()
@@ -66,7 +77,9 @@
     }
 
     @Test
-    fun testPopupDoesntExceedMaxWidth() = testPopup { popupMenu ->
+    fun testPopupDoesntExceedMaxWidth() = testPopup { activity, popupMenu ->
+        popupMenu.setAdapter(FakeListAdapter())
+        popupMenu.width = ViewGroup.LayoutParams.MATCH_PARENT
         testDisplayMetrics.widthPixels = DISPLAY_WIDTH_WIDE
 
         popupMenu.show()
@@ -75,7 +88,9 @@
     }
 
     @Test
-    fun testPopupMarginsWidthLessMax() = testPopup { popupMenu ->
+    fun testPopupMarginsWidthLessMax() = testPopup { activity, popupMenu ->
+        popupMenu.setAdapter(FakeListAdapter())
+        popupMenu.width = ViewGroup.LayoutParams.MATCH_PARENT
         testDisplayMetrics.widthPixels = DISPLAY_WIDTH_NARROW
 
         popupMenu.show()
@@ -83,10 +98,32 @@
         assertThat(popupMenu.width).isEqualTo(DISPLAY_WIDTH_NARROW - 2 * HORIZONTAL_MARGIN)
     }
 
-    private fun testPopup(test: (popup: ControlsPopupMenu) -> Unit) {
+    @Test
+    fun testWrapContentDoesntExceedMax() = testPopup { activity, popupMenu ->
+        popupMenu.setAdapter(
+            FakeListAdapter(
+                listOf(
+                    FakeListAdapterItem({ _, _, _ ->
+                        View(activity).apply { minimumWidth = MAX_WIDTH + 1 }
+                    })
+                )
+            )
+        )
+        popupMenu.width = ViewGroup.LayoutParams.WRAP_CONTENT
+        testDisplayMetrics.widthPixels = DISPLAY_WIDTH_NARROW
+
+        popupMenu.show()
+
+        assertThat(popupMenu.width).isEqualTo(DISPLAY_WIDTH_NARROW - 2 * HORIZONTAL_MARGIN)
+    }
+
+    private fun testPopup(test: (activity: Activity, popup: ControlsPopupMenu) -> Unit) {
         activityScenarioRule.scenario.onActivity { activity ->
             val testActivity = setupActivity(activity)
-            test(ControlsPopupMenu(testActivity).apply { anchorView = View(testActivity) })
+            test(
+                testActivity,
+                ControlsPopupMenu(testActivity).apply { anchorView = View(testActivity) }
+            )
         }
     }
 
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 f21ea3d..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,11 +44,14 @@
 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
@@ -119,6 +122,7 @@
     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?>
@@ -189,6 +193,9 @@
         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,
@@ -207,6 +214,7 @@
             alternateBouncerInteractor,
             faceDetectBuffer,
             faceAuthBuffer,
+            keyguardTransitionInteractor,
             dumpManager,
         )
     }
@@ -772,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()
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/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index 9a0bd9e..f206409 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -255,10 +255,10 @@
                 mLocalBluetoothLeBroadcast);
         mIsBroadcasting = true;
 
-        mMediaOutputBaseDialogImpl.onStart();
+        mMediaOutputBaseDialogImpl.start();
         verify(mLocalBluetoothLeBroadcast).registerServiceCallBack(any(), any());
 
-        mMediaOutputBaseDialogImpl.onStop();
+        mMediaOutputBaseDialogImpl.stop();
         verify(mLocalBluetoothLeBroadcast).unregisterServiceCallBack(any());
     }
 
@@ -269,8 +269,8 @@
                 mLocalBluetoothLeBroadcast);
         mIsBroadcasting = false;
 
-        mMediaOutputBaseDialogImpl.onStart();
-        mMediaOutputBaseDialogImpl.onStop();
+        mMediaOutputBaseDialogImpl.start();
+        mMediaOutputBaseDialogImpl.stop();
 
         verify(mLocalBluetoothLeBroadcast, never()).registerServiceCallBack(any(), any());
         verify(mLocalBluetoothLeBroadcast, never()).unregisterServiceCallBack(any());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index ba29ca5..22a5b21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -63,8 +63,10 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
+import org.mockito.Mockito.doNothing
 import org.mockito.Mockito.isNull
 import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyZeroInteractions
 import org.mockito.MockitoAnnotations
@@ -75,7 +77,9 @@
 internal class NoteTaskControllerTest : SysuiTestCase() {
 
     @Mock private lateinit var context: Context
+    @Mock private lateinit var workProfileContext: Context
     @Mock private lateinit var packageManager: PackageManager
+    @Mock private lateinit var workProfilePackageManager: PackageManager
     @Mock private lateinit var resolver: NoteTaskInfoResolver
     @Mock private lateinit var bubbles: Bubbles
     @Mock private lateinit var keyguardManager: KeyguardManager
@@ -107,6 +111,7 @@
             .thenReturn(listOf(NOTE_TASK_PACKAGE_NAME))
         whenever(activityManager.getRunningTasks(anyInt())).thenReturn(emptyList())
         whenever(userManager.isManagedProfile(workUserInfo.id)).thenReturn(true)
+        whenever(context.resources).thenReturn(getContext().resources)
     }
 
     private fun createNoteTaskController(
@@ -337,14 +342,14 @@
     }
 
     @Test
-    fun showNoteTask_intentResolverReturnsNull_shouldDoNothing() {
+    fun showNoteTask_intentResolverReturnsNull_shouldShowToast() {
         whenever(resolver.resolveInfo(any(), any())).thenReturn(null)
+        val noteTaskController = spy(createNoteTaskController())
+        doNothing().whenever(noteTaskController).showNoDefaultNotesAppToast()
 
-        createNoteTaskController()
-            .showNoteTask(
-                entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
-            )
+        noteTaskController.showNoteTask(entryPoint = NoteTaskEntryPoint.TAIL_BUTTON)
 
+        verify(noteTaskController).showNoDefaultNotesAppToast()
         verifyZeroInteractions(context, bubbles, eventLogger)
     }
 
@@ -373,17 +378,17 @@
     @Test
     fun showNoteTask_keyboardShortcut_shouldStartActivity() {
         val expectedInfo =
-                NOTE_TASK_INFO.copy(
-                        entryPoint = NoteTaskEntryPoint.KEYBOARD_SHORTCUT,
-                        isKeyguardLocked = true,
-                )
+            NOTE_TASK_INFO.copy(
+                entryPoint = NoteTaskEntryPoint.KEYBOARD_SHORTCUT,
+                isKeyguardLocked = true,
+            )
         whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
         whenever(resolver.resolveInfo(any(), any())).thenReturn(expectedInfo)
 
         createNoteTaskController()
-                .showNoteTask(
-                        entryPoint = expectedInfo.entryPoint!!,
-                )
+            .showNoteTask(
+                entryPoint = expectedInfo.entryPoint!!,
+            )
 
         val intentCaptor = argumentCaptor<Intent>()
         val userCaptor = argumentCaptor<UserHandle>()
@@ -393,9 +398,9 @@
             assertThat(intent.`package`).isEqualTo(NOTE_TASK_PACKAGE_NAME)
             assertThat(intent.flags and FLAG_ACTIVITY_NEW_TASK).isEqualTo(FLAG_ACTIVITY_NEW_TASK)
             assertThat(intent.flags and FLAG_ACTIVITY_MULTIPLE_TASK)
-                    .isEqualTo(FLAG_ACTIVITY_MULTIPLE_TASK)
+                .isEqualTo(FLAG_ACTIVITY_MULTIPLE_TASK)
             assertThat(intent.flags and FLAG_ACTIVITY_NEW_DOCUMENT)
-                    .isEqualTo(FLAG_ACTIVITY_NEW_DOCUMENT)
+                .isEqualTo(FLAG_ACTIVITY_NEW_DOCUMENT)
             assertThat(intent.getBooleanExtra(Intent.EXTRA_USE_STYLUS_MODE, true)).isFalse()
         }
         assertThat(userCaptor.value).isEqualTo(userTracker.userHandle)
@@ -407,7 +412,7 @@
     // region setNoteTaskShortcutEnabled
     @Test
     fun setNoteTaskShortcutEnabled_setTrue() {
-        createNoteTaskController().setNoteTaskShortcutEnabled(value = true)
+        createNoteTaskController().setNoteTaskShortcutEnabled(value = true, userTracker.userHandle)
 
         val argument = argumentCaptor<ComponentName>()
         verify(context.packageManager)
@@ -422,7 +427,7 @@
 
     @Test
     fun setNoteTaskShortcutEnabled_setFalse() {
-        createNoteTaskController().setNoteTaskShortcutEnabled(value = false)
+        createNoteTaskController().setNoteTaskShortcutEnabled(value = false, userTracker.userHandle)
 
         val argument = argumentCaptor<ComponentName>()
         verify(context.packageManager)
@@ -434,6 +439,47 @@
         assertThat(argument.value.className)
             .isEqualTo(CreateNoteTaskShortcutActivity::class.java.name)
     }
+
+    @Test
+    fun setNoteTaskShortcutEnabled_workProfileUser_setTrue() {
+        whenever(context.createContextAsUser(eq(workUserInfo.userHandle), any()))
+            .thenReturn(workProfileContext)
+        whenever(workProfileContext.packageManager).thenReturn(workProfilePackageManager)
+        userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
+
+        createNoteTaskController().setNoteTaskShortcutEnabled(value = true, workUserInfo.userHandle)
+
+        val argument = argumentCaptor<ComponentName>()
+        verify(workProfilePackageManager)
+            .setComponentEnabledSetting(
+                argument.capture(),
+                eq(COMPONENT_ENABLED_STATE_ENABLED),
+                eq(PackageManager.DONT_KILL_APP),
+            )
+        assertThat(argument.value.className)
+            .isEqualTo(CreateNoteTaskShortcutActivity::class.java.name)
+    }
+
+    @Test
+    fun setNoteTaskShortcutEnabled_workProfileUser_setFalse() {
+        whenever(context.createContextAsUser(eq(workUserInfo.userHandle), any()))
+            .thenReturn(workProfileContext)
+        whenever(workProfileContext.packageManager).thenReturn(workProfilePackageManager)
+        userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
+
+        createNoteTaskController()
+            .setNoteTaskShortcutEnabled(value = false, workUserInfo.userHandle)
+
+        val argument = argumentCaptor<ComponentName>()
+        verify(workProfilePackageManager)
+            .setComponentEnabledSetting(
+                argument.capture(),
+                eq(COMPONENT_ENABLED_STATE_DISABLED),
+                eq(PackageManager.DONT_KILL_APP),
+            )
+        assertThat(argument.value.className)
+            .isEqualTo(CreateNoteTaskShortcutActivity::class.java.name)
+    }
     // endregion
 
     // region keyguard policy
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
index ec4daee..28ed9d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
@@ -20,9 +20,11 @@
 import android.view.KeyEvent
 import androidx.test.runner.AndroidJUnit4
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.wm.shell.bubbles.Bubbles
 import java.util.Optional
@@ -46,6 +48,7 @@
     @Mock lateinit var roleManager: RoleManager
     private val clock = FakeSystemClock()
     private val executor = FakeExecutor(clock)
+    private val userTracker = FakeUserTracker()
 
     @Before
     fun setUp() {
@@ -63,6 +66,7 @@
             isEnabled = isEnabled,
             roleManager = roleManager,
             backgroundExecutor = executor,
+            userTracker = userTracker,
         )
     }
 
@@ -71,7 +75,7 @@
     fun initialize() {
         createNoteTaskInitializer().initialize()
 
-        verify(controller).setNoteTaskShortcutEnabled(true)
+        verify(controller).setNoteTaskShortcutEnabled(eq(true), eq(userTracker.userHandle))
         verify(commandQueue).addCallback(any())
         verify(roleManager).addOnRoleHoldersChangedListenerAsUser(any(), any(), any())
     }
@@ -80,7 +84,7 @@
     fun initialize_flagDisabled() {
         createNoteTaskInitializer(isEnabled = false).initialize()
 
-        verify(controller, never()).setNoteTaskShortcutEnabled(any())
+        verify(controller, never()).setNoteTaskShortcutEnabled(any(), any())
         verify(commandQueue, never()).addCallback(any())
         verify(roleManager, never()).addOnRoleHoldersChangedListenerAsUser(any(), any(), any())
     }
@@ -89,7 +93,7 @@
     fun initialize_bubblesNotPresent() {
         createNoteTaskInitializer(bubbles = null).initialize()
 
-        verify(controller, never()).setNoteTaskShortcutEnabled(any())
+        verify(controller, never()).setNoteTaskShortcutEnabled(any(), any())
         verify(commandQueue, never()).addCallback(any())
         verify(roleManager, never()).addOnRoleHoldersChangedListenerAsUser(any(), any(), any())
     }
@@ -98,24 +102,36 @@
     // region handleSystemKey
     @Test
     fun handleSystemKey_receiveValidSystemKey_shouldShowNoteTask() {
-        createNoteTaskInitializer().callbacks.handleSystemKey(KeyEvent(KeyEvent.ACTION_DOWN,
-                KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL))
+        createNoteTaskInitializer()
+            .callbacks
+            .handleSystemKey(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL))
 
         verify(controller).showNoteTask(entryPoint = NoteTaskEntryPoint.TAIL_BUTTON)
     }
 
     @Test
     fun handleSystemKey_receiveKeyboardShortcut_shouldShowNoteTask() {
-        createNoteTaskInitializer().callbacks.handleSystemKey(KeyEvent(0, 0, KeyEvent.ACTION_DOWN,
-                KeyEvent.KEYCODE_N, 0, KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON))
+        createNoteTaskInitializer()
+            .callbacks
+            .handleSystemKey(
+                KeyEvent(
+                    0,
+                    0,
+                    KeyEvent.ACTION_DOWN,
+                    KeyEvent.KEYCODE_N,
+                    0,
+                    KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON
+                )
+            )
 
         verify(controller).showNoteTask(entryPoint = NoteTaskEntryPoint.KEYBOARD_SHORTCUT)
     }
-    
+
     @Test
     fun handleSystemKey_receiveInvalidSystemKey_shouldDoNothing() {
-        createNoteTaskInitializer().callbacks.handleSystemKey(KeyEvent(KeyEvent.ACTION_DOWN,
-                KeyEvent.KEYCODE_UNKNOWN))
+        createNoteTaskInitializer()
+            .callbacks
+            .handleSystemKey(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_UNKNOWN))
 
         verifyZeroInteractions(controller)
     }
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 4bfd6a2..068d933 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -303,6 +303,7 @@
     protected ArgumentCaptor<NotificationStackScrollLayout.OnEmptySpaceClickListener>
             mEmptySpaceClickListenerCaptor;
     @Mock protected ActivityStarter mActivityStarter;
+    @Mock protected KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
 
     protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
     protected KeyguardInteractor mKeyguardInteractor;
@@ -600,7 +601,8 @@
                 mDumpManager,
                 mKeyuardLongPressViewModel,
                 mKeyguardInteractor,
-                mActivityStarter);
+                mActivityStarter,
+                mKeyguardFaceAuthInteractor);
         mNotificationPanelViewController.initDependencies(
                 mCentralSurfaces,
                 null,
@@ -667,7 +669,7 @@
                 mFeatureFlags,
                 mInteractionJankMonitor,
                 mShadeLog,
-                mock(KeyguardFaceAuthInteractor.class)
+                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/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
index b043e97..76f7401 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
@@ -16,6 +16,8 @@
 package com.android.systemui.shade
 
 import android.animation.Animator
+import android.app.AlarmManager
+import android.app.PendingIntent
 import android.app.StatusBarManager
 import android.content.Context
 import android.content.res.Resources
@@ -40,8 +42,10 @@
 import com.android.systemui.demomode.DemoMode
 import com.android.systemui.demomode.DemoModeController
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.qs.ChipVisibilityListener
 import com.android.systemui.qs.HeaderPrivacyIconsController
+import com.android.systemui.shade.ShadeHeaderController.Companion.DEFAULT_CLOCK_INTENT
 import com.android.systemui.shade.ShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT
 import com.android.systemui.shade.ShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT
 import com.android.systemui.shade.ShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
@@ -52,6 +56,7 @@
 import com.android.systemui.statusbar.phone.StatusIconContainer
 import com.android.systemui.statusbar.policy.Clock
 import com.android.systemui.statusbar.policy.FakeConfigurationController
+import com.android.systemui.statusbar.policy.NextAlarmController
 import com.android.systemui.statusbar.policy.VariableDateView
 import com.android.systemui.statusbar.policy.VariableDateViewController
 import com.android.systemui.util.mockito.any
@@ -114,6 +119,8 @@
 
     @Mock private lateinit var demoModeController: DemoModeController
     @Mock private lateinit var qsBatteryModeController: QsBatteryModeController
+    @Mock private lateinit var nextAlarmController: NextAlarmController
+    @Mock private lateinit var activityStarter: ActivityStarter
 
     @JvmField @Rule val mockitoRule = MockitoJUnit.rule()
     var viewVisibility = View.GONE
@@ -181,6 +188,8 @@
                 combinedShadeHeadersConstraintManager,
                 demoModeController,
                 qsBatteryModeController,
+                nextAlarmController,
+                activityStarter,
             )
         whenever(view.isAttachedToWindow).thenReturn(true)
         shadeHeaderController.init()
@@ -828,6 +837,28 @@
         verify(carrierGroup).setPaddingRelative(514, 0, 0, 0)
     }
 
+    @Test
+    fun launchClock_launchesDefaultIntentWhenNoAlarmSet() {
+        shadeHeaderController.launchClockActivity()
+
+        verify(activityStarter).postStartActivityDismissingKeyguard(DEFAULT_CLOCK_INTENT, 0)
+    }
+
+    @Test
+    fun launchClock_launchesNextAlarmWhenExists() {
+        val pendingIntent = mock<PendingIntent>()
+        val aci = AlarmManager.AlarmClockInfo(12345, pendingIntent)
+        val captor =
+            ArgumentCaptor.forClass(NextAlarmController.NextAlarmChangeCallback::class.java)
+
+        verify(nextAlarmController).addCallback(capture(captor))
+        captor.value.onNextAlarmChanged(aci)
+
+        shadeHeaderController.launchClockActivity()
+
+        verify(activityStarter).postStartActivityDismissingKeyguard(pendingIntent)
+    }
+
     private fun View.executeLayoutChange(
         left: Int,
         top: Int,
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/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index eb0b9b3..760a90b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -54,6 +54,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -120,6 +121,8 @@
     @Mock private CommandQueue mCommandQueue;
     @Mock private KeyguardLogger mLogger;
 
+    @Mock private NotificationMediaManager mNotificationMediaManager;
+
     private TestNotificationPanelViewStateProvider mNotificationPanelViewStateProvider;
     private KeyguardStatusBarView mKeyguardStatusBarView;
     private KeyguardStatusBarViewController mController;
@@ -167,7 +170,8 @@
                 mSecureSettings,
                 mCommandQueue,
                 mFakeExecutor,
-                mLogger
+                mLogger,
+                mNotificationMediaManager
         );
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt
index a2828d33..1cc0bd3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt
@@ -25,7 +25,6 @@
 import android.view.IWindowManager
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.DumpManager
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
@@ -52,7 +51,6 @@
     @get:Rule var expect: Expect = Expect.create()
 
     @Mock private lateinit var windowManager: IWindowManager
-    @Mock private lateinit var dumpManager: DumpManager
     @Mock private lateinit var wallpaperManager: WallpaperManager
 
     private lateinit var provider: LetterboxBackgroundProvider
@@ -65,8 +63,7 @@
 
         setUpWallpaperManager()
         provider =
-            LetterboxBackgroundProvider(
-                windowManager, fakeExecutor, dumpManager, wallpaperManager, mainHandler)
+            LetterboxBackgroundProvider(windowManager, fakeExecutor, wallpaperManager, mainHandler)
     }
 
     private fun setUpWallpaperManager() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
index 6c0f6c2..07ffd11 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
@@ -14,6 +14,8 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
@@ -44,6 +46,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.concurrent.atomic.AtomicBoolean;
+
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
 @SmallTest
@@ -109,4 +113,31 @@
         dialog.dismiss();
         assertFalse(dialog.isShowing());
     }
+
+    @Test public void startAndStopAreCalled() {
+        AtomicBoolean calledStart = new AtomicBoolean(false);
+        AtomicBoolean calledStop = new AtomicBoolean(false);
+        SystemUIDialog dialog = new SystemUIDialog(mContext) {
+            @Override
+            protected void start() {
+                calledStart.set(true);
+            }
+
+            @Override
+            protected void stop() {
+                calledStop.set(true);
+            }
+        };
+
+        assertThat(calledStart.get()).isFalse();
+        assertThat(calledStop.get()).isFalse();
+
+        dialog.show();
+        assertThat(calledStart.get()).isTrue();
+        assertThat(calledStop.get()).isFalse();
+
+        dialog.dismiss();
+        assertThat(calledStart.get()).isTrue();
+        assertThat(calledStop.get()).isTrue();
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/widget/FakeListAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/widget/FakeListAdapter.kt
new file mode 100644
index 0000000..231373b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/widget/FakeListAdapter.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.widget
+
+import android.view.View
+import android.view.ViewGroup
+import android.widget.BaseAdapter
+
+class FakeListAdapter(private var items: List<FakeListAdapterItem> = emptyList()) : BaseAdapter() {
+
+    fun setItems(items: List<FakeListAdapterItem>) {
+        this.items = items
+        notifyDataSetChanged()
+    }
+
+    override fun getCount(): Int = items.size
+
+    override fun getItem(position: Int): Any = items[position].data
+
+    override fun getItemId(position: Int): Long = items[position].id
+
+    override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View =
+        items[position].view(position, convertView, parent)
+
+    class FakeListAdapterItem(
+        /** Result returned in [Adapter#getView] */
+        val view: (position: Int, convertView: View?, parent: ViewGroup?) -> View,
+        /** Returned in [Adapter#getItemId] */
+        val id: Long = 0,
+        /** Returned in [Adapter#getItem] */
+        val data: Any = Unit,
+    )
+}
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/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/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/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/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 8ad76cb..1426cfd 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -342,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() {
@@ -763,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()) {
@@ -1283,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();
@@ -1301,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);
@@ -1311,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);
@@ -1406,7 +1430,7 @@
     @GuardedBy({"mAm", "mProcLock"})
     void unfreezeAppLSP(ProcessRecord app, @UnfreezeReason int reason) {
         synchronized (mFreezerLock) {
-            unfreezeAppInternalLSP(app, reason);
+            unfreezeAppInternalLSP(app, reason, false);
         }
     }
 
@@ -2080,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);
diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
index f233107..e8c8f6d 100644
--- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
+++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
@@ -75,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.
      */
@@ -195,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/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 d1cbbfc..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() */
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/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/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java
index ec70c89..6d6ed72 100644
--- a/services/core/java/com/android/server/display/PersistentDataStore.java
+++ b/services/core/java/com/android/server/display/PersistentDataStore.java
@@ -306,8 +306,11 @@
     }
 
     public boolean setBrightness(DisplayDevice displayDevice, float brightness) {
+        if (displayDevice == null || !displayDevice.hasStableUniqueId()) {
+            return false;
+        }
         final String displayDeviceUniqueId = displayDevice.getUniqueId();
-        if (!displayDevice.hasStableUniqueId() || displayDeviceUniqueId == null) {
+        if (displayDeviceUniqueId == null) {
             return false;
         }
         final DisplayState state = getDisplayState(displayDeviceUniqueId, true);
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/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/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/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/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/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index c1552ab..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);
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/Task.java b/services/core/java/com/android/server/wm/Task.java
index 5f9ba3f..9363eb5 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3252,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);
 
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 652c297..76b0e7b 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -2835,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/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 327483e..da54b15 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -119,24 +119,20 @@
         final DisplayInfo mDisplayInfo;
         final DisplayFrames mDisplayFrames;
         final Configuration mRotatedOverrideConfiguration;
-        final SeamlessRotator mRotator;
+
         /**
          * The tokens that share the same transform. Their end time of transform are the same. The
          * list should at least contain the token who creates this state.
          */
         final ArrayList<WindowToken> mAssociatedTokens = new ArrayList<>(3);
-        final ArrayList<WindowContainer<?>> mRotatedContainers = new ArrayList<>(3);
+
         boolean mIsTransforming = true;
 
         FixedRotationTransformState(DisplayInfo rotatedDisplayInfo,
-                DisplayFrames rotatedDisplayFrames, Configuration rotatedConfig,
-                int currentRotation) {
+                DisplayFrames rotatedDisplayFrames, Configuration rotatedConfig) {
             mDisplayInfo = rotatedDisplayInfo;
             mDisplayFrames = rotatedDisplayFrames;
             mRotatedOverrideConfiguration = rotatedConfig;
-            // This will use unrotate as rotate, so the new and old rotation are inverted.
-            mRotator = new SeamlessRotator(rotatedDisplayInfo.rotation, currentRotation,
-                    rotatedDisplayInfo, true /* applyFixedTransformationHint */);
         }
 
         /**
@@ -144,10 +140,8 @@
          * showing the window in a display with different rotation.
          */
         void transform(WindowContainer<?> container) {
-            mRotator.unrotate(container.getPendingTransaction(), container);
-            if (!mRotatedContainers.contains(container)) {
-                mRotatedContainers.add(container);
-            }
+            // The default implementation assumes shell transition is enabled, so the transform
+            // is done by getOrCreateFixedRotationLeash().
         }
 
         /**
@@ -155,6 +149,40 @@
          * be called when the window has the same rotation as display.
          */
         void resetTransform() {
+            for (int i = mAssociatedTokens.size() - 1; i >= 0; --i) {
+                mAssociatedTokens.get(i).removeFixedRotationLeash();
+            }
+        }
+
+        /** The state may not only be used by self. Make sure to leave the influence by others. */
+        void disassociate(WindowToken token) {
+            mAssociatedTokens.remove(token);
+        }
+    }
+
+    private static class FixedRotationTransformStateLegacy extends FixedRotationTransformState {
+        final SeamlessRotator mRotator;
+        final ArrayList<WindowContainer<?>> mRotatedContainers = new ArrayList<>(3);
+
+        FixedRotationTransformStateLegacy(DisplayInfo rotatedDisplayInfo,
+                DisplayFrames rotatedDisplayFrames, Configuration rotatedConfig,
+                int currentRotation) {
+            super(rotatedDisplayInfo, rotatedDisplayFrames, rotatedConfig);
+            // This will use unrotate as rotate, so the new and old rotation are inverted.
+            mRotator = new SeamlessRotator(rotatedDisplayInfo.rotation, currentRotation,
+                    rotatedDisplayInfo, true /* applyFixedTransformationHint */);
+        }
+
+        @Override
+        void transform(WindowContainer<?> container) {
+            mRotator.unrotate(container.getPendingTransaction(), container);
+            if (!mRotatedContainers.contains(container)) {
+                mRotatedContainers.add(container);
+            }
+        }
+
+        @Override
+        void resetTransform() {
             for (int i = mRotatedContainers.size() - 1; i >= 0; i--) {
                 final WindowContainer<?> c = mRotatedContainers.get(i);
                 // If the window is detached (no parent), its surface may have been released.
@@ -164,9 +192,9 @@
             }
         }
 
-        /** The state may not only be used by self. Make sure to leave the influence by others. */
+        @Override
         void disassociate(WindowToken token) {
-            mAssociatedTokens.remove(token);
+            super.disassociate(token);
             mRotatedContainers.remove(token);
         }
     }
@@ -437,8 +465,11 @@
         if (mFixedRotationTransformState != null) {
             mFixedRotationTransformState.disassociate(this);
         }
-        mFixedRotationTransformState = new FixedRotationTransformState(info, displayFrames,
-                new Configuration(config), mDisplayContent.getRotation());
+        config = new Configuration(config);
+        mFixedRotationTransformState = mTransitionController.isShellTransitionsEnabled()
+                ? new FixedRotationTransformState(info, displayFrames, config)
+                : new FixedRotationTransformStateLegacy(info, displayFrames, config,
+                        mDisplayContent.getRotation());
         mFixedRotationTransformState.mAssociatedTokens.add(this);
         mDisplayContent.getDisplayPolicy().simulateLayoutDisplay(displayFrames);
         onFixedRotationStatePrepared();
@@ -508,14 +539,7 @@
         if (state == null) {
             return;
         }
-        if (!mTransitionController.isShellTransitionsEnabled()) {
-            state.resetTransform();
-        } else {
-            // Remove all the leashes
-            for (int i = state.mAssociatedTokens.size() - 1; i >= 0; --i) {
-                state.mAssociatedTokens.get(i).removeFixedRotationLeash();
-            }
-        }
+        state.resetTransform();
         // Clear the flag so if the display will be updated to the same orientation, the transform
         // won't take effect.
         state.mIsTransforming = false;
@@ -589,7 +613,9 @@
     void removeFixedRotationLeash() {
         if (mFixedRotationTransformLeash == null) return;
         final SurfaceControl.Transaction t = getSyncTransaction();
-        t.reparent(getSurfaceControl(), getParentSurfaceControl());
+        if (mSurfaceControl != null) {
+            t.reparent(mSurfaceControl, getParentSurfaceControl());
+        }
         t.remove(mFixedRotationTransformLeash);
         mFixedRotationTransformLeash = null;
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 3d5686d..702602a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -792,7 +792,7 @@
                             admin.getUserId());
             if (receivers.isEmpty()) {
                 Log.i(TAG, "Couldn't find any receivers that handle ACTION_DEVICE_POLICY_SET_RESULT"
-                        + "in package " + admin.getPackageName());
+                        + " in package " + admin.getPackageName());
                 return;
             }
 
@@ -845,7 +845,7 @@
                             admin.getUserId());
             if (receivers.isEmpty()) {
                 Log.i(TAG, "Couldn't find any receivers that handle ACTION_DEVICE_POLICY_CHANGED"
-                        + "in package " + admin.getPackageName());
+                        + " in package " + admin.getPackageName());
                 return;
             }
 
@@ -868,7 +868,7 @@
         for (ResolveInfo resolveInfo : receivers) {
             if (!Manifest.permission.BIND_DEVICE_ADMIN.equals(
                     resolveInfo.activityInfo.permission)) {
-                Log.w(TAG, "Receiver " + resolveInfo.activityInfo + " is not protected by"
+                Log.w(TAG, "Receiver " + resolveInfo.activityInfo + " is not protected by "
                         + "BIND_DEVICE_ADMIN permission!");
                 continue;
             }
@@ -1066,6 +1066,11 @@
      * Removes all local policies for the provided {@code userId}.
      */
     private void removeLocalPoliciesForUser(int userId) {
+        if (!mLocalPolicies.contains(userId)) {
+            // No policies on user
+            return;
+        }
+
         Set<PolicyKey> localPolicies = new HashSet<>(mLocalPolicies.get(userId).keySet());
         for (PolicyKey policy : localPolicies) {
             PolicyState<?> policyState = mLocalPolicies.get(userId).get(policy);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ed0bcc3..5cad4e2 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -856,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";
@@ -13287,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");
             }
         }
     }
@@ -13315,14 +13318,13 @@
                 /* 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 =
@@ -13340,7 +13342,6 @@
                     userId);
         }
     }
-
     private void setGlobalUserRestrictionInternal(
             EnforcingAdmin admin, String key, boolean enabled) {
         PolicyDefinition<Boolean> policyDefinition =
@@ -14724,7 +14725,7 @@
         }
 
         final int userId = mInjector.userHandleGetCallingUserId();
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             LockTaskPolicy policy = mDevicePolicyEngine.getResolvedPolicy(
                     PolicyDefinition.LOCK_TASK, userId);
             if (policy == null) {
@@ -16111,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) {
@@ -16142,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) {
@@ -17376,6 +17365,7 @@
                     caller.getUserId());
             admin = enforcingAdmin.getActiveAdmin();
         } else {
+            Objects.requireNonNull(who, "ComponentName is null");
             Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
         }
 
@@ -17407,6 +17397,7 @@
                     caller.getUserId());
             admin = enforcingAdmin.getActiveAdmin();
         } else {
+            Objects.requireNonNull(who, "ComponentName is null");
             Preconditions.checkCallingUser(isManagedProfile(caller.getUserId()));
             Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
 
@@ -22572,53 +22563,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,
@@ -22639,13 +22630,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,
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/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index bc3f5ab..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);
@@ -836,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));
@@ -905,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,
@@ -955,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));
@@ -1004,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,
@@ -1036,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(
@@ -1088,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);
 
@@ -1129,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);
 
@@ -1152,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.
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 90e6a2d..7be1d7c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -77,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;
@@ -146,6 +147,7 @@
 
     private Context mContext;
     private HandlerThread mHandlerThread;
+    private TestLooperManager mLooper;
     private AtomicInteger mNextPid;
 
     @Mock
@@ -206,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);
@@ -360,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,
@@ -671,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 {
@@ -1156,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
@@ -1200,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();
@@ -1240,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();
 
@@ -1621,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
@@ -1745,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,
@@ -1836,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,
@@ -1878,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));
@@ -2048,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/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/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/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/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/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/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/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/TestableResources.java b/tests/testables/src/android/testing/TestableResources.java
index c60f07d..27d5b66 100644
--- a/tests/testables/src/android/testing/TestableResources.java
+++ b/tests/testables/src/android/testing/TestableResources.java
@@ -59,7 +59,8 @@
      * Since resource ids are unique there is a single addOverride that will override the value
      * whenever it is gotten regardless of which method is used (i.e. getColor or getDrawable).
      * </p>
-     * @param id The resource id to be overridden
+     *
+     * @param id    The resource id to be overridden
      * @param value The value of the resource, null to cause a {@link Resources.NotFoundException}
      *              when gotten.
      */
@@ -74,28 +75,33 @@
      * cause a {@link Resources.NotFoundException} whereas removing the override will actually
      * switch back to returning the default/real value of the resource.
      * </p>
-     * @param id
      */
     public void removeOverride(int id) {
         mOverrides.remove(id);
     }
 
     private Object answer(InvocationOnMock invocationOnMock) throws Throwable {
-        try {
-            int id = invocationOnMock.getArgument(0);
-            int index = mOverrides.indexOfKey(id);
-            if (index >= 0) {
-                Object value = mOverrides.valueAt(index);
-                if (value == null) throw new Resources.NotFoundException();
-                return value;
+        // Only try to override methods with an integer first argument
+        if (invocationOnMock.getArguments().length > 0) {
+            Object argument = invocationOnMock.getArgument(0);
+            if (argument instanceof Integer) {
+                try {
+                    int id = (Integer)argument;
+                    int index = mOverrides.indexOfKey(id);
+                    if (index >= 0) {
+                        Object value = mOverrides.valueAt(index);
+                        if (value == null) throw new Resources.NotFoundException();
+                        return value;
+                    }
+                } catch (Resources.NotFoundException e) {
+                    // Let through NotFoundException.
+                    throw e;
+                } catch (Throwable t) {
+                    // Generic catching for the many things that can go wrong, fall back to
+                    // the real implementation.
+                    Log.i(TAG, "Falling back to default resources call " + t);
+                }
             }
-        } catch (Resources.NotFoundException e) {
-            // Let through NotFoundException.
-            throw e;
-        } catch (Throwable t) {
-            // Generic catching for the many things that can go wrong, fall back to
-            // the real implementation.
-            Log.i(TAG, "Falling back to default resources call " + t);
         }
         return invocationOnMock.callRealMethod();
     }